fix: allow to access private fields after `this` reassignment

pull/11487/head
paoloricciuti 2 years ago
parent 34079a0ec5
commit 144aec74d3

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: allow to access private fields after `this` reassignment

@ -1503,6 +1503,18 @@ const common_visitors = {
return;
}
}
},
ThisExpression(node, { next, path }) {
const parent = path.at(-1);
if (parent?.type === 'MemberExpression' && parent.object === node) {
return;
}
const class_declaration = path.find((path_element) => path_element.type === 'ClassDeclaration');
if (class_declaration && class_declaration.type === 'ClassDeclaration') {
class_declaration.metadata = {
needs_private_getters: true
};
}
}
};

@ -12,12 +12,20 @@ export const global_visitors = {
return serialize_get_binding(node, state);
}
},
MemberExpression(node, { state, next }) {
MemberExpression(node, { state, next, path }) {
const class_declaration = path.find((path_element) => path_element.type === 'ClassDeclaration');
const needs_private_getters =
class_declaration?.type === 'ClassDeclaration' &&
!!class_declaration.metadata?.needs_private_getters;
if (node.object.type === 'ThisExpression') {
// rewrite `this.#foo` as `this.#foo.v` inside a constructor
if (node.property.type === 'PrivateIdentifier') {
const field = state.private_state.get(node.property.name);
if (field) {
if (field && needs_private_getters && state.in_constructor) {
return b.member(b.member(b.this, field.id), b.id('v'));
}
if (field && !needs_private_getters) {
return state.in_constructor ? b.member(node, b.id('v')) : b.call('$.get', node);
}
}

@ -8,7 +8,12 @@ import { regex_invalid_identifier_chars } from '../../../patterns.js';
/** @type {import('../types.js').ComponentVisitors} */
export const javascript_visitors_runes = {
ClassBody(node, { state, visit }) {
ClassBody(node, { state, visit, path }) {
const parent = path.at(-1);
const needs_private_getters =
parent?.type === 'ClassDeclaration' && !!parent.metadata?.needs_private_getters;
/** @type {Map<string, import('../types.js').StateField>} */
const public_state = new Map();
@ -64,6 +69,19 @@ export const javascript_visitors_runes = {
}
}
if (needs_private_getters) {
// each `#foo = $state()` needs a backing `#_foo` field is the class needs private getters
for (const [name, field] of private_state) {
let deconflicted = name;
while (private_ids.includes(deconflicted)) {
deconflicted = '_' + deconflicted;
}
private_ids.push(deconflicted);
field.id = b.private_id(deconflicted);
}
}
// each `foo = $state()` needs a backing `#foo` field
for (const [name, field] of public_state) {
let deconflicted = name;
@ -121,7 +139,7 @@ export const javascript_visitors_runes = {
value = b.call('$.source');
}
if (is_private) {
if (is_private && !needs_private_getters) {
body.push(b.prop_def(field.id, value));
} else {
// #foo;

@ -103,4 +103,10 @@ declare module 'estree' {
scope: Scope;
};
}
interface ClassDeclaration {
metadata?: {
needs_private_getters: boolean;
};
}
}

@ -0,0 +1,10 @@
import { test } from '../../test';
export default test({
compileOptions: {
dev: true
},
async test({ assert, logs }) {
assert.deepEqual(logs, ['init', 1]);
}
});

@ -0,0 +1,17 @@
<script>
class Counter {
#count = $state();
constructor(){
const instance = this;
instance.#count = 1;
}
get count(){
return this.#count;
}
}
const counter = new Counter();
$inspect(counter.count)
</script>
Loading…
Cancel
Save