|
1 |
| -/** @import { Expression, ExpressionStatement, Identifier, MemberExpression, SequenceExpression, Statement, Super } from 'estree' */ |
| 1 | +/** @import { AssignmentExpression, Expression, ExpressionStatement, Identifier, MemberExpression, SequenceExpression, Literal, Super, UpdateExpression } from 'estree' */ |
2 | 2 | /** @import { AST, ExpressionMetadata } from '#compiler' */
|
3 |
| -/** @import { ComponentClientTransformState } from '../../types' */ |
| 3 | +/** @import { ComponentClientTransformState, Context } from '../../types' */ |
4 | 4 | import { walk } from 'zimmerframe';
|
5 | 5 | import { object } from '../../../../../utils/ast.js';
|
6 | 6 | import * as b from '../../../../../utils/builders.js';
|
7 | 7 | import { sanitize_template_string } from '../../../../../utils/sanitize_template_string.js';
|
8 | 8 | import { regex_is_valid_identifier } from '../../../../patterns.js';
|
9 | 9 | import is_reference from 'is-reference';
|
10 |
| -import { locator } from '../../../../../state.js'; |
| 10 | +import { dev, is_ignored, locator } from '../../../../../state.js'; |
11 | 11 | import { create_derived } from '../../utils.js';
|
12 | 12 |
|
13 | 13 | /**
|
@@ -295,3 +295,60 @@ export function validate_binding(state, binding, expression) {
|
295 | 295 | )
|
296 | 296 | );
|
297 | 297 | }
|
| 298 | + |
| 299 | +/** |
| 300 | + * In dev mode validate mutations to props |
| 301 | + * @param {AssignmentExpression | UpdateExpression} node |
| 302 | + * @param {Context} context |
| 303 | + * @param {Expression} expression |
| 304 | + */ |
| 305 | +export function validate_mutation(node, context, expression) { |
| 306 | + let left = /** @type {Expression | Super} */ ( |
| 307 | + node.type === 'AssignmentExpression' ? node.left : node.argument |
| 308 | + ); |
| 309 | + |
| 310 | + if (!dev || left.type !== 'MemberExpression' || is_ignored(node, 'ownership_invalid_mutation')) { |
| 311 | + return expression; |
| 312 | + } |
| 313 | + |
| 314 | + const name = object(left); |
| 315 | + if (!name) return expression; |
| 316 | + |
| 317 | + const binding = context.state.scope.get(name.name); |
| 318 | + if (binding?.kind !== 'prop' && binding?.kind !== 'bindable_prop') return expression; |
| 319 | + |
| 320 | + const state = /** @type {ComponentClientTransformState} */ (context.state); |
| 321 | + state.analysis.needs_mutation_validation = true; |
| 322 | + |
| 323 | + /** @type {Array<Identifier | Literal>} */ |
| 324 | + const path = []; |
| 325 | + |
| 326 | + while (left.type === 'MemberExpression') { |
| 327 | + if (left.property.type === 'Literal') { |
| 328 | + path.unshift(left.property); |
| 329 | + } else if (left.property.type === 'Identifier') { |
| 330 | + if (left.computed) { |
| 331 | + path.unshift(left.property); |
| 332 | + } else { |
| 333 | + path.unshift(b.literal(left.property.name)); |
| 334 | + } |
| 335 | + } else { |
| 336 | + return expression; |
| 337 | + } |
| 338 | + |
| 339 | + left = left.object; |
| 340 | + } |
| 341 | + |
| 342 | + path.unshift(b.literal(name.name)); |
| 343 | + |
| 344 | + const loc = locator(/** @type {number} */ (left.start)); |
| 345 | + |
| 346 | + return b.call( |
| 347 | + '$$ownership_validator.mutation', |
| 348 | + b.literal(binding.prop_alias), |
| 349 | + b.array(path), |
| 350 | + expression, |
| 351 | + loc && b.literal(loc.line), |
| 352 | + loc && b.literal(loc.column) |
| 353 | + ); |
| 354 | +} |
0 commit comments