mirror of https://github.com/sveltejs/svelte
parent
f78ca010f6
commit
86884827ea
@ -1,474 +1,496 @@
|
|||||||
import Component from '../../Component';
|
|
||||||
import { walk } from 'estree-walker';
|
import { walk } from 'estree-walker';
|
||||||
import is_reference from 'is-reference';
|
import is_reference from 'is-reference';
|
||||||
import flatten_reference from '../../utils/flatten_reference';
|
import flatten_reference from '../../utils/flatten_reference.js';
|
||||||
import { create_scopes, Scope, extract_names } from '../../utils/scope';
|
import { create_scopes, extract_names } from '../../utils/scope.js';
|
||||||
import { sanitize } from '../../../utils/names';
|
import { sanitize } from '../../../utils/names.js';
|
||||||
import TemplateScope from './TemplateScope';
|
import get_object from '../../utils/get_object.js';
|
||||||
import get_object from '../../utils/get_object';
|
import is_dynamic from '../../render_dom/wrappers/shared/is_dynamic.js';
|
||||||
import Block from '../../render_dom/Block';
|
|
||||||
import is_dynamic from '../../render_dom/wrappers/shared/is_dynamic';
|
|
||||||
import { b } from 'code-red';
|
import { b } from 'code-red';
|
||||||
import { invalidate } from '../../render_dom/invalidate';
|
import { invalidate } from '../../render_dom/invalidate.js';
|
||||||
import { Node, FunctionExpression, Identifier } from 'estree';
|
import { is_reserved_keyword } from '../../utils/reserved_keywords.js';
|
||||||
import { INode } from '../interfaces';
|
import replace_object from '../../utils/replace_object.js';
|
||||||
import { is_reserved_keyword } from '../../utils/reserved_keywords';
|
import is_contextual from './is_contextual.js';
|
||||||
import replace_object from '../../utils/replace_object';
|
import { clone } from '../../../utils/clone.js';
|
||||||
import is_contextual from './is_contextual';
|
import compiler_errors from '../../compiler_errors.js';
|
||||||
import EachBlock from '../EachBlock';
|
|
||||||
import { clone } from '../../../utils/clone';
|
|
||||||
import compiler_errors from '../../compiler_errors';
|
|
||||||
|
|
||||||
type Owner = INode;
|
|
||||||
|
|
||||||
const regex_contains_term_function_expression = /FunctionExpression/;
|
const regex_contains_term_function_expression = /FunctionExpression/;
|
||||||
|
|
||||||
export default class Expression {
|
export default class Expression {
|
||||||
type: 'Expression' = 'Expression' as const;
|
|
||||||
component: Component;
|
/** @type {'Expression'} */
|
||||||
owner: Owner;
|
type = ('Expression');
|
||||||
node: Node;
|
|
||||||
references: Set<string> = new Set();
|
/** @type {import('../../Component.js').default} */
|
||||||
dependencies: Set<string> = new Set();
|
component;
|
||||||
contextual_dependencies: Set<string> = new Set();
|
|
||||||
|
/** @type {Owner} */
|
||||||
template_scope: TemplateScope;
|
owner;
|
||||||
scope: Scope;
|
|
||||||
scope_map: WeakMap<Node, Scope>;
|
/** @type {import('estree').Node} */
|
||||||
|
node;
|
||||||
declarations: Array<Node | Node[]> = [];
|
|
||||||
uses_context = false;
|
/** @type {Set<string>} */
|
||||||
|
references = new Set();
|
||||||
manipulated: Node;
|
|
||||||
|
/** @type {Set<string>} */
|
||||||
constructor(
|
dependencies = new Set();
|
||||||
component: Component,
|
|
||||||
owner: Owner,
|
/** @type {Set<string>} */
|
||||||
template_scope: TemplateScope,
|
contextual_dependencies = new Set();
|
||||||
info: Node,
|
|
||||||
lazy?: boolean
|
/** @type {import('./TemplateScope.js').default} */
|
||||||
) {
|
template_scope;
|
||||||
// TODO revert to direct property access in prod?
|
|
||||||
Object.defineProperties(this, {
|
/** @type {import('../../utils/scope.js').Scope} */
|
||||||
component: {
|
scope;
|
||||||
value: component
|
|
||||||
}
|
/** @type {WeakMap<import('estree').Node, import('../../utils/scope.js').Scope>} */
|
||||||
});
|
scope_map;
|
||||||
|
|
||||||
this.node = info;
|
/** @type {Array<import('estree').Node | import('estree').Node[]>} */
|
||||||
this.template_scope = template_scope;
|
declarations = [];
|
||||||
this.owner = owner;
|
/** */
|
||||||
|
uses_context = false;
|
||||||
const { dependencies, contextual_dependencies, references } = this;
|
|
||||||
|
/** @type {import('estree').Node} */
|
||||||
let { map, scope } = create_scopes(info);
|
manipulated;
|
||||||
this.scope = scope;
|
|
||||||
this.scope_map = map;
|
/**
|
||||||
|
* @param {import('../../Component.js').default} component *
|
||||||
const expression = this;
|
* @param {Owner} owner *
|
||||||
let function_expression;
|
* @param {import('./TemplateScope.js').default} template_scope *
|
||||||
|
* @param {import('estree').Node} info *
|
||||||
// discover dependencies, but don't change the code yet
|
* @param {boolean} [lazy] undefined
|
||||||
walk(info, {
|
*/
|
||||||
enter(node: any, parent: any, key: string) {
|
constructor(component, owner, template_scope, info, lazy) {
|
||||||
// don't manipulate shorthand props twice
|
// TODO revert to direct property access in prod?
|
||||||
if (key === 'key' && parent.shorthand) return;
|
Object.defineProperties(this, {
|
||||||
// don't manipulate `import.meta`, `new.target`
|
component: {
|
||||||
if (node.type === 'MetaProperty') return this.skip();
|
value: component
|
||||||
|
}
|
||||||
if (map.has(node)) {
|
});
|
||||||
scope = map.get(node);
|
this.node = info;
|
||||||
}
|
this.template_scope = template_scope;
|
||||||
|
this.owner = owner;
|
||||||
if (!function_expression && regex_contains_term_function_expression.test(node.type)) {
|
const { dependencies, contextual_dependencies, references } = this;
|
||||||
function_expression = node;
|
let { map, scope } = create_scopes(info);
|
||||||
}
|
this.scope = scope;
|
||||||
|
this.scope_map = map;
|
||||||
if (is_reference(node, parent)) {
|
const expression = this;
|
||||||
const { name, nodes } = flatten_reference(node);
|
let function_expression;
|
||||||
references.add(name);
|
// discover dependencies, but don't change the code yet
|
||||||
|
walk(info, {
|
||||||
if (scope.has(name)) return;
|
|
||||||
|
/**
|
||||||
if (name[0] === '$') {
|
* @param {any} node
|
||||||
const store_name = name.slice(1);
|
* @param {any} parent
|
||||||
if (template_scope.names.has(store_name) || scope.has(store_name)) {
|
* @param {string} key
|
||||||
return component.error(node, compiler_errors.contextual_store);
|
*/
|
||||||
}
|
enter(node, parent, key) {
|
||||||
}
|
// don't manipulate shorthand props twice
|
||||||
|
if (key === 'key' && parent.shorthand)
|
||||||
if (template_scope.is_let(name)) {
|
return;
|
||||||
if (!lazy) {
|
// don't manipulate `import.meta`, `new.target`
|
||||||
contextual_dependencies.add(name);
|
if (node.type === 'MetaProperty')
|
||||||
dependencies.add(name);
|
return this.skip();
|
||||||
}
|
if (map.has(node)) {
|
||||||
} else if (template_scope.names.has(name)) {
|
scope = map.get(node);
|
||||||
expression.uses_context = true;
|
}
|
||||||
|
if (!function_expression && regex_contains_term_function_expression.test(node.type)) {
|
||||||
contextual_dependencies.add(name);
|
function_expression = node;
|
||||||
|
}
|
||||||
const owner = template_scope.get_owner(name);
|
if (is_reference(node, parent)) {
|
||||||
const is_index = owner.type === 'EachBlock' && owner.key && name === owner.index;
|
const { name, nodes } = flatten_reference(node);
|
||||||
|
references.add(name);
|
||||||
if (!lazy || is_index) {
|
if (scope.has(name))
|
||||||
template_scope.dependencies_for_name
|
return;
|
||||||
.get(name)
|
if (name[0] === '$') {
|
||||||
.forEach((name) => dependencies.add(name));
|
const store_name = name.slice(1);
|
||||||
}
|
if (template_scope.names.has(store_name) || scope.has(store_name)) {
|
||||||
} else {
|
return component.error(node, compiler_errors.contextual_store);
|
||||||
if (!lazy) {
|
}
|
||||||
dependencies.add(name);
|
}
|
||||||
}
|
if (template_scope.is_let(name)) {
|
||||||
|
if (!lazy) {
|
||||||
component.add_reference(node, name);
|
contextual_dependencies.add(name);
|
||||||
component.warn_if_undefined(name, nodes[0], template_scope);
|
dependencies.add(name);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
this.skip();
|
else if (template_scope.names.has(name)) {
|
||||||
}
|
expression.uses_context = true;
|
||||||
|
contextual_dependencies.add(name);
|
||||||
// track any assignments from template expressions as mutable
|
const owner = template_scope.get_owner(name);
|
||||||
let names;
|
const is_index = owner.type === 'EachBlock' && owner.key && name === owner.index;
|
||||||
let deep = false;
|
if (!lazy || is_index) {
|
||||||
|
template_scope.dependencies_for_name
|
||||||
if (function_expression) {
|
.get(name)
|
||||||
if (node.type === 'AssignmentExpression') {
|
.forEach(/** @param {any} name */ (name) => dependencies.add(name));
|
||||||
deep = node.left.type === 'MemberExpression';
|
}
|
||||||
names = extract_names(deep ? get_object(node.left) : node.left);
|
}
|
||||||
} else if (node.type === 'UpdateExpression') {
|
else {
|
||||||
deep = node.argument.type === 'MemberExpression';
|
if (!lazy) {
|
||||||
names = extract_names(get_object(node.argument));
|
dependencies.add(name);
|
||||||
}
|
}
|
||||||
}
|
component.add_reference(node, name);
|
||||||
|
component.warn_if_undefined(name, nodes[0], template_scope);
|
||||||
if (names) {
|
}
|
||||||
names.forEach((name) => {
|
this.skip();
|
||||||
if (template_scope.names.has(name)) {
|
}
|
||||||
if (template_scope.is_const(name)) {
|
// track any assignments from template expressions as mutable
|
||||||
component.error(node, compiler_errors.invalid_const_update(name));
|
let names;
|
||||||
}
|
let deep = false;
|
||||||
|
if (function_expression) {
|
||||||
template_scope.dependencies_for_name.get(name).forEach((name) => {
|
if (node.type === 'AssignmentExpression') {
|
||||||
const variable = component.var_lookup.get(name);
|
deep = node.left.type === 'MemberExpression';
|
||||||
if (variable) variable[deep ? 'mutated' : 'reassigned'] = true;
|
names = extract_names(deep ? get_object(node.left) : node.left);
|
||||||
});
|
}
|
||||||
const each_block = template_scope.get_owner(name);
|
else if (node.type === 'UpdateExpression') {
|
||||||
(each_block as EachBlock).has_binding = true;
|
deep = node.argument.type === 'MemberExpression';
|
||||||
} else {
|
names = extract_names(get_object(node.argument));
|
||||||
component.add_reference(node, name);
|
}
|
||||||
|
}
|
||||||
const variable = component.var_lookup.get(name);
|
if (names) {
|
||||||
|
names.forEach(/** @param {any} name */ (name) => {
|
||||||
if (variable) {
|
if (template_scope.names.has(name)) {
|
||||||
variable[deep ? 'mutated' : 'reassigned'] = true;
|
if (template_scope.is_const(name)) {
|
||||||
}
|
component.error(node, compiler_errors.invalid_const_update(name));
|
||||||
|
}
|
||||||
const declaration: any = scope.find_owner(name)?.declarations.get(name);
|
template_scope.dependencies_for_name.get(name).forEach(/** @param {any} name */ (name) => {
|
||||||
|
const variable = component.var_lookup.get(name);
|
||||||
if (declaration) {
|
if (variable)
|
||||||
if (declaration.kind === 'const' && !deep) {
|
variable[deep ? 'mutated' : 'reassigned'] = true;
|
||||||
component.error(node, {
|
});
|
||||||
code: 'assignment-to-const',
|
const each_block = template_scope.get_owner(name);
|
||||||
message: 'You are assigning to a const'
|
( /** @type {import('../EachBlock.js').default} */(each_block)).has_binding = true;
|
||||||
});
|
}
|
||||||
}
|
else {
|
||||||
} else if (variable && variable.writable === false && !deep) {
|
component.add_reference(node, name);
|
||||||
component.error(node, {
|
const variable = component.var_lookup.get(name);
|
||||||
code: 'assignment-to-const',
|
if (variable) {
|
||||||
message: 'You are assigning to a const'
|
variable[deep ? 'mutated' : 'reassigned'] = true;
|
||||||
});
|
}
|
||||||
}
|
|
||||||
}
|
/** @type {any} */
|
||||||
});
|
const declaration = scope.find_owner(name)?.declarations.get(name);
|
||||||
}
|
if (declaration) {
|
||||||
},
|
if (declaration.kind === 'const' && !deep) {
|
||||||
|
component.error(node, {
|
||||||
leave(node: Node) {
|
code: 'assignment-to-const',
|
||||||
if (map.has(node)) {
|
message: 'You are assigning to a const'
|
||||||
scope = scope.parent;
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (node === function_expression) {
|
else if (variable && variable.writable === false && !deep) {
|
||||||
function_expression = null;
|
component.error(node, {
|
||||||
}
|
code: 'assignment-to-const',
|
||||||
}
|
message: 'You are assigning to a const'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
dynamic_dependencies() {
|
});
|
||||||
return Array.from(this.dependencies).filter((name) => {
|
}
|
||||||
if (this.template_scope.is_let(name)) return true;
|
},
|
||||||
if (is_reserved_keyword(name)) return true;
|
|
||||||
|
/** @param {import('estree').Node} node */
|
||||||
const variable = this.component.var_lookup.get(name);
|
leave(node) {
|
||||||
return is_dynamic(variable);
|
if (map.has(node)) {
|
||||||
});
|
scope = scope.parent;
|
||||||
}
|
}
|
||||||
|
if (node === function_expression) {
|
||||||
dynamic_contextual_dependencies() {
|
function_expression = null;
|
||||||
return Array.from(this.contextual_dependencies).filter((name) => {
|
}
|
||||||
return Array.from(this.template_scope.dependencies_for_name.get(name)).some(
|
}
|
||||||
(variable_name) => {
|
});
|
||||||
const variable = this.component.var_lookup.get(variable_name);
|
}
|
||||||
return is_dynamic(variable);
|
dynamic_dependencies() {
|
||||||
}
|
return Array.from(this.dependencies).filter(/** @param {any} name */ (name) => {
|
||||||
);
|
if (this.template_scope.is_let(name))
|
||||||
});
|
return true;
|
||||||
}
|
if (is_reserved_keyword(name))
|
||||||
|
return true;
|
||||||
// TODO move this into a render-dom wrapper?
|
const variable = this.component.var_lookup.get(name);
|
||||||
manipulate(block?: Block, ctx?: string | void) {
|
return is_dynamic(variable);
|
||||||
// TODO ideally we wouldn't end up calling this method
|
});
|
||||||
// multiple times
|
}
|
||||||
if (this.manipulated) return this.manipulated;
|
dynamic_contextual_dependencies() {
|
||||||
|
return Array.from(this.contextual_dependencies).filter(/** @param {any} name */ (name) => {
|
||||||
const { component, declarations, scope_map: map, template_scope, owner } = this;
|
return Array.from(this.template_scope.dependencies_for_name.get(name)).some(
|
||||||
let scope = this.scope;
|
|
||||||
|
/** @param {any} variable_name */
|
||||||
let function_expression;
|
(variable_name) => {
|
||||||
|
const variable = this.component.var_lookup.get(variable_name);
|
||||||
let dependencies: Set<string>;
|
return is_dynamic(variable);
|
||||||
let contextual_dependencies: Set<string>;
|
});
|
||||||
|
});
|
||||||
const node = walk(this.node, {
|
}
|
||||||
enter(node: any, parent: any) {
|
// TODO move this into a render-dom wrapper?
|
||||||
if (node.type === 'Property' && node.shorthand) {
|
|
||||||
node.value = clone(node.value);
|
/**
|
||||||
node.shorthand = false;
|
* @param {import('../../render_dom/Block.js').default} [block]
|
||||||
}
|
* @param {string | void} [ctx]
|
||||||
|
*/
|
||||||
if (map.has(node)) {
|
manipulate(block, ctx) {
|
||||||
scope = map.get(node);
|
// TODO ideally we wouldn't end up calling this method
|
||||||
}
|
// multiple times
|
||||||
|
if (this.manipulated)
|
||||||
if (node.type === 'Identifier' && is_reference(node, parent)) {
|
return this.manipulated;
|
||||||
const { name } = flatten_reference(node);
|
const { component, declarations, scope_map: map, template_scope, owner } = this;
|
||||||
|
let scope = this.scope;
|
||||||
if (scope.has(name)) return;
|
let function_expression;
|
||||||
|
|
||||||
if (function_expression) {
|
/** @type {Set<string>} */
|
||||||
if (template_scope.names.has(name)) {
|
let dependencies;
|
||||||
contextual_dependencies.add(name);
|
|
||||||
|
/** @type {Set<string>} */
|
||||||
template_scope.dependencies_for_name.get(name).forEach((dependency) => {
|
let contextual_dependencies;
|
||||||
dependencies.add(dependency);
|
const node = walk(this.node, {
|
||||||
});
|
|
||||||
} else {
|
/**
|
||||||
dependencies.add(name);
|
* @param {any} node
|
||||||
component.add_reference(node, name); // TODO is this redundant/misplaced?
|
* @param {any} parent
|
||||||
}
|
*/
|
||||||
} else if (is_contextual(component, template_scope, name)) {
|
enter(node, parent) {
|
||||||
const reference = block.renderer.reference(node, ctx);
|
if (node.type === 'Property' && node.shorthand) {
|
||||||
this.replace(reference);
|
node.value = clone(node.value);
|
||||||
}
|
node.shorthand = false;
|
||||||
|
}
|
||||||
this.skip();
|
if (map.has(node)) {
|
||||||
}
|
scope = map.get(node);
|
||||||
|
}
|
||||||
if (!function_expression) {
|
if (node.type === 'Identifier' && is_reference(node, parent)) {
|
||||||
if (node.type === 'AssignmentExpression') {
|
const { name } = flatten_reference(node);
|
||||||
// TODO should this be a warning/error? `<p>{foo = 1}</p>`
|
if (scope.has(name))
|
||||||
}
|
return;
|
||||||
|
if (function_expression) {
|
||||||
if (node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') {
|
if (template_scope.names.has(name)) {
|
||||||
function_expression = node;
|
contextual_dependencies.add(name);
|
||||||
dependencies = new Set();
|
template_scope.dependencies_for_name.get(name).forEach(/** @param {any} dependency */ (dependency) => {
|
||||||
contextual_dependencies = new Set();
|
dependencies.add(dependency);
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
},
|
else {
|
||||||
|
dependencies.add(name);
|
||||||
leave(node: Node, parent: Node) {
|
component.add_reference(node, name); // TODO is this redundant/misplaced?
|
||||||
if (map.has(node)) scope = scope.parent;
|
}
|
||||||
|
}
|
||||||
if (node === function_expression) {
|
else if (is_contextual(component, template_scope, name)) {
|
||||||
const id = component.get_unique_name(sanitize(get_function_name(node, owner)));
|
const reference = block.renderer.reference(node, ctx);
|
||||||
|
this.replace(reference);
|
||||||
const declaration = b`const ${id} = ${node}`;
|
}
|
||||||
const extract_functions = () => {
|
this.skip();
|
||||||
const deps = Array.from(contextual_dependencies);
|
}
|
||||||
const function_expression = node as FunctionExpression;
|
if (!function_expression) {
|
||||||
|
if (node.type === 'AssignmentExpression') {
|
||||||
const has_args = function_expression.params.length > 0;
|
// TODO should this be a warning/error? `<p>{foo = 1}</p>`
|
||||||
function_expression.params = [
|
}
|
||||||
...deps.map((name) => ({ type: 'Identifier', name } as Identifier)),
|
if (node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') {
|
||||||
...function_expression.params
|
function_expression = node;
|
||||||
];
|
dependencies = new Set();
|
||||||
|
contextual_dependencies = new Set();
|
||||||
const context_args = deps.map((name) => block.renderer.reference(name, ctx));
|
}
|
||||||
|
}
|
||||||
component.partly_hoisted.push(declaration);
|
},
|
||||||
|
|
||||||
block.renderer.add_to_context(id.name);
|
/**
|
||||||
const callee = block.renderer.reference(id);
|
* @param {import('estree').Node} node
|
||||||
|
* @param {import('estree').Node} parent
|
||||||
this.replace(id as any);
|
*/
|
||||||
|
leave(node, parent) {
|
||||||
const func_declaration = has_args
|
if (map.has(node))
|
||||||
? b`function ${id}(...args) {
|
scope = scope.parent;
|
||||||
|
if (node === function_expression) {
|
||||||
|
const id = component.get_unique_name(sanitize(get_function_name(node, owner)));
|
||||||
|
const declaration = b `const ${id} = ${node}`;
|
||||||
|
const extract_functions = () => {
|
||||||
|
const deps = Array.from(contextual_dependencies);
|
||||||
|
const function_expression = /** @type {import('estree').FunctionExpression} */ (node);
|
||||||
|
const has_args = function_expression.params.length > 0;
|
||||||
|
function_expression.params = [
|
||||||
|
...deps.map(/** @param {any} name */ (name) => ( /** @type {import('estree').Identifier} */({ type: 'Identifier', name }))),
|
||||||
|
...function_expression.params
|
||||||
|
];
|
||||||
|
const context_args = deps.map(/** @param {any} name */ (name) => block.renderer.reference(name, ctx));
|
||||||
|
component.partly_hoisted.push(declaration);
|
||||||
|
block.renderer.add_to_context(id.name);
|
||||||
|
const callee = block.renderer.reference(id);
|
||||||
|
this.replace(/** @type {any} */ (id));
|
||||||
|
const func_declaration = has_args
|
||||||
|
? b `function ${id}(...args) {
|
||||||
return ${callee}(${context_args}, ...args);
|
return ${callee}(${context_args}, ...args);
|
||||||
}`
|
}`
|
||||||
: b`function ${id}() {
|
: b `function ${id}() {
|
||||||
return ${callee}(${context_args});
|
return ${callee}(${context_args});
|
||||||
}`;
|
}`;
|
||||||
return { deps, func_declaration };
|
return { deps, func_declaration };
|
||||||
};
|
};
|
||||||
|
if (owner.type === 'ConstTag') {
|
||||||
if (owner.type === 'ConstTag') {
|
// we need a combo block/init recipe
|
||||||
// we need a combo block/init recipe
|
if (contextual_dependencies.size === 0) {
|
||||||
if (contextual_dependencies.size === 0) {
|
let child_scope = scope;
|
||||||
let child_scope = scope;
|
walk(node, {
|
||||||
walk(node, {
|
|
||||||
enter(node: Node, parent: any) {
|
/**
|
||||||
if (map.has(node)) child_scope = map.get(node);
|
* @param {import('estree').Node} node
|
||||||
if (node.type === 'Identifier' && is_reference(node, parent)) {
|
* @param {any} parent
|
||||||
if (child_scope.has(node.name)) return;
|
*/
|
||||||
this.replace(block.renderer.reference(node, ctx));
|
enter(node, parent) {
|
||||||
}
|
if (map.has(node))
|
||||||
},
|
child_scope = map.get(node);
|
||||||
leave(node: Node) {
|
if (node.type === 'Identifier' && is_reference(node, parent)) {
|
||||||
if (map.has(node)) child_scope = child_scope.parent;
|
if (child_scope.has(node.name))
|
||||||
}
|
return;
|
||||||
});
|
this.replace(block.renderer.reference(node, ctx));
|
||||||
} else {
|
}
|
||||||
const { func_declaration } = extract_functions();
|
},
|
||||||
this.replace(func_declaration[0]);
|
|
||||||
}
|
/** @param {import('estree').Node} node */
|
||||||
} else if (dependencies.size === 0 && contextual_dependencies.size === 0) {
|
leave(node) {
|
||||||
// we can hoist this out of the component completely
|
if (map.has(node))
|
||||||
component.fully_hoisted.push(declaration);
|
child_scope = child_scope.parent;
|
||||||
|
}
|
||||||
this.replace(id as any);
|
});
|
||||||
|
}
|
||||||
component.add_var(node, {
|
else {
|
||||||
name: id.name,
|
const { func_declaration } = extract_functions();
|
||||||
internal: true,
|
this.replace(func_declaration[0]);
|
||||||
hoistable: true,
|
}
|
||||||
referenced: true
|
}
|
||||||
});
|
else if (dependencies.size === 0 && contextual_dependencies.size === 0) {
|
||||||
} else if (contextual_dependencies.size === 0) {
|
// we can hoist this out of the component completely
|
||||||
// function can be hoisted inside the component init
|
component.fully_hoisted.push(declaration);
|
||||||
component.partly_hoisted.push(declaration);
|
this.replace(/** @type {any} */ (id));
|
||||||
|
component.add_var(node, {
|
||||||
block.renderer.add_to_context(id.name);
|
name: id.name,
|
||||||
this.replace(block.renderer.reference(id));
|
internal: true,
|
||||||
} else {
|
hoistable: true,
|
||||||
// we need a combo block/init recipe
|
referenced: true
|
||||||
const { deps, func_declaration } = extract_functions();
|
});
|
||||||
|
}
|
||||||
if (owner.type === 'Attribute' && owner.parent.name === 'slot') {
|
else if (contextual_dependencies.size === 0) {
|
||||||
const dep_scopes = new Set<INode>(deps.map((name) => template_scope.get_owner(name)));
|
// function can be hoisted inside the component init
|
||||||
// find the nearest scopes
|
component.partly_hoisted.push(declaration);
|
||||||
let node: INode = owner.parent;
|
block.renderer.add_to_context(id.name);
|
||||||
while (node && !dep_scopes.has(node)) {
|
this.replace(block.renderer.reference(id));
|
||||||
node = node.parent;
|
}
|
||||||
}
|
else {
|
||||||
|
// we need a combo block/init recipe
|
||||||
const func_expression = func_declaration[0];
|
const { deps, func_declaration } = extract_functions();
|
||||||
|
if (owner.type === 'Attribute' && owner.parent.name === 'slot') {
|
||||||
if (node.type === 'InlineComponent' || node.type === 'SlotTemplate') {
|
const dep_scopes = new Set(deps.map(/** @param {any} name */ (name) => template_scope.get_owner(name)));
|
||||||
// <Comp let:data />
|
// find the nearest scopes
|
||||||
this.replace(func_expression);
|
|
||||||
} else {
|
/** @type {import('../interfaces.js').INode} */
|
||||||
// {#each}, {#await}
|
let node = owner.parent;
|
||||||
const func_id = component.get_unique_name(id.name + '_func');
|
while (node && !dep_scopes.has(node)) {
|
||||||
block.renderer.add_to_context(func_id.name, true);
|
node = node.parent;
|
||||||
// rename #ctx -> child_ctx;
|
}
|
||||||
walk(func_expression, {
|
const func_expression = func_declaration[0];
|
||||||
enter(node: Node) {
|
if (node.type === 'InlineComponent' || node.type === 'SlotTemplate') {
|
||||||
if (node.type === 'Identifier' && node.name === '#ctx') {
|
// <Comp let:data />
|
||||||
node.name = 'child_ctx';
|
this.replace(func_expression);
|
||||||
}
|
}
|
||||||
}
|
else {
|
||||||
});
|
// {#each}, {#await}
|
||||||
// add to get_xxx_context
|
const func_id = component.get_unique_name(id.name + '_func');
|
||||||
// child_ctx[x] = function () { ... }
|
block.renderer.add_to_context(func_id.name, true);
|
||||||
(template_scope.get_owner(deps[0]) as EachBlock).contexts.push({
|
// rename #ctx -> child_ctx;
|
||||||
type: 'DestructuredVariable',
|
walk(func_expression, {
|
||||||
key: func_id,
|
|
||||||
modifier: () => func_expression,
|
/** @param {import('estree').Node} node */
|
||||||
default_modifier: (node) => node
|
enter(node) {
|
||||||
});
|
if (node.type === 'Identifier' && node.name === '#ctx') {
|
||||||
this.replace(block.renderer.reference(func_id));
|
node.name = 'child_ctx';
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
declarations.push(func_declaration);
|
});
|
||||||
}
|
// add to get_xxx_context
|
||||||
}
|
// child_ctx[x] = function () { ... }
|
||||||
|
( /** @type {import('../EachBlock.js').default} */(template_scope.get_owner(deps[0]))).contexts.push({
|
||||||
function_expression = null;
|
type: 'DestructuredVariable',
|
||||||
dependencies = null;
|
key: func_id,
|
||||||
contextual_dependencies = null;
|
modifier: () => func_expression,
|
||||||
|
default_modifier: /** @param {any} node */ (node) => node
|
||||||
if (parent && parent.type === 'Property') {
|
});
|
||||||
parent.method = false;
|
this.replace(block.renderer.reference(func_id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
if (node.type === 'AssignmentExpression' || node.type === 'UpdateExpression') {
|
declarations.push(func_declaration);
|
||||||
const assignee = node.type === 'AssignmentExpression' ? node.left : node.argument;
|
}
|
||||||
|
}
|
||||||
const object_name = get_object(assignee).name;
|
function_expression = null;
|
||||||
|
dependencies = null;
|
||||||
if (scope.has(object_name)) return;
|
contextual_dependencies = null;
|
||||||
|
if (parent && parent.type === 'Property') {
|
||||||
// normally (`a = 1`, `b.c = 2`), there'll be a single name
|
parent.method = false;
|
||||||
// (a or b). In destructuring cases (`[d, e] = [e, d]`) there
|
}
|
||||||
// may be more, in which case we need to tack the extra ones
|
}
|
||||||
// onto the initial function call
|
if (node.type === 'AssignmentExpression' || node.type === 'UpdateExpression') {
|
||||||
const names = new Set(extract_names(assignee as Node));
|
const assignee = node.type === 'AssignmentExpression' ? node.left : node.argument;
|
||||||
|
const object_name = get_object(assignee).name;
|
||||||
const traced: Set<string> = new Set();
|
if (scope.has(object_name))
|
||||||
names.forEach((name) => {
|
return;
|
||||||
const dependencies = template_scope.dependencies_for_name.get(name);
|
// normally (`a = 1`, `b.c = 2`), there'll be a single name
|
||||||
if (dependencies) {
|
// (a or b). In destructuring cases (`[d, e] = [e, d]`) there
|
||||||
dependencies.forEach((name) => traced.add(name));
|
// may be more, in which case we need to tack the extra ones
|
||||||
} else {
|
// onto the initial function call
|
||||||
traced.add(name);
|
const names = new Set(extract_names(/** @type {import('estree').Node} */ (assignee)));
|
||||||
}
|
|
||||||
});
|
/** @type {Set<string>} */
|
||||||
|
const traced = new Set();
|
||||||
const context = block.bindings.get(object_name);
|
names.forEach(/** @param {any} name */ (name) => {
|
||||||
|
const dependencies = template_scope.dependencies_for_name.get(name);
|
||||||
if (context) {
|
if (dependencies) {
|
||||||
// for `{#each array as item}`
|
dependencies.forEach(/** @param {any} name */ (name) => traced.add(name));
|
||||||
// replace `item = 1` to `each_array[each_index] = 1`, this allow us to mutate the array
|
}
|
||||||
// rather than mutating the local `item` variable
|
else {
|
||||||
const { snippet, object, property } = context;
|
traced.add(name);
|
||||||
const replaced: any = replace_object(assignee, snippet);
|
}
|
||||||
if (node.type === 'AssignmentExpression') {
|
});
|
||||||
node.left = replaced;
|
const context = block.bindings.get(object_name);
|
||||||
} else {
|
if (context) {
|
||||||
node.argument = replaced;
|
// for `{#each array as item}`
|
||||||
}
|
// replace `item = 1` to `each_array[each_index] = 1`, this allow us to mutate the array
|
||||||
contextual_dependencies.add(object.name);
|
// rather than mutating the local `item` variable
|
||||||
contextual_dependencies.add(property.name);
|
const { snippet, object, property } = context;
|
||||||
}
|
|
||||||
|
/** @type {any} */
|
||||||
this.replace(invalidate(block.renderer, scope, node, traced));
|
const replaced = replace_object(assignee, snippet);
|
||||||
}
|
if (node.type === 'AssignmentExpression') {
|
||||||
}
|
node.left = replaced;
|
||||||
});
|
}
|
||||||
|
else {
|
||||||
if (declarations.length > 0) {
|
node.argument = replaced;
|
||||||
block.maintain_context = true;
|
}
|
||||||
declarations.forEach((declaration) => {
|
contextual_dependencies.add(object.name);
|
||||||
block.chunks.init.push(declaration);
|
contextual_dependencies.add(property.name);
|
||||||
});
|
}
|
||||||
}
|
this.replace(invalidate(block.renderer, scope, node, traced));
|
||||||
|
}
|
||||||
return (this.manipulated = node as Node);
|
}
|
||||||
}
|
});
|
||||||
|
if (declarations.length > 0) {
|
||||||
|
block.maintain_context = true;
|
||||||
|
declarations.forEach(/** @param {any} declaration */ (declaration) => {
|
||||||
|
block.chunks.init.push(declaration);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return (this.manipulated = /** @type {import('estree').Node} */ (node));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {any} _node
|
||||||
|
* @param {any} parent
|
||||||
|
*/
|
||||||
function get_function_name(_node, parent) {
|
function get_function_name(_node, parent) {
|
||||||
if (parent.type === 'EventHandler') {
|
if (parent.type === 'EventHandler') {
|
||||||
return `${parent.name}_handler`;
|
return `${parent.name}_handler`;
|
||||||
}
|
}
|
||||||
|
if (parent.type === 'Action') {
|
||||||
|
return `${parent.name}_function`;
|
||||||
|
}
|
||||||
|
return 'func';
|
||||||
|
}
|
||||||
|
|
||||||
if (parent.type === 'Action') {
|
|
||||||
return `${parent.name}_function`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'func';
|
/** @typedef {INode} Owner */
|
||||||
}
|
|
||||||
|
|||||||
Loading…
Reference in new issue