Lint and format src/compiler/compile/nodes/shared/Expression.js

pull/8569/head
Simon Holthausen 3 years ago
parent b0f9d079c3
commit 160cdc948b

@ -14,9 +14,8 @@ import { clone } from '../../../utils/clone.js';
import compiler_errors from '../../compiler_errors.js'; import compiler_errors from '../../compiler_errors.js';
const regex_contains_term_function_expression = /FunctionExpression/; const regex_contains_term_function_expression = /FunctionExpression/;
export default class Expression { export default class Expression {
/** @type {'Expression'} */ /** @type {'Expression'} */
type = ('Expression'); type = 'Expression';
/** @type {import('../../Component.js').default} */ /** @type {import('../../Component.js').default} */
component; component;
@ -78,7 +77,6 @@ export default class Expression {
let function_expression; let function_expression;
// discover dependencies, but don't change the code yet // discover dependencies, but don't change the code yet
walk(info, { walk(info, {
/** /**
* @param {any} node * @param {any} node
* @param {any} parent * @param {any} parent
@ -86,11 +84,9 @@ export default class Expression {
*/ */
enter(node, parent, key) { enter(node, parent, key) {
// don't manipulate shorthand props twice // don't manipulate shorthand props twice
if (key === 'key' && parent.shorthand) if (key === 'key' && parent.shorthand) return;
return;
// don't manipulate `import.meta`, `new.target` // don't manipulate `import.meta`, `new.target`
if (node.type === 'MetaProperty') if (node.type === 'MetaProperty') return this.skip();
return this.skip();
if (map.has(node)) { if (map.has(node)) {
scope = map.get(node); scope = map.get(node);
} }
@ -100,8 +96,7 @@ export default class Expression {
if (is_reference(node, parent)) { if (is_reference(node, parent)) {
const { name, nodes } = flatten_reference(node); const { name, nodes } = flatten_reference(node);
references.add(name); references.add(name);
if (scope.has(name)) if (scope.has(name)) return;
return;
if (name[0] === '$') { if (name[0] === '$') {
const store_name = name.slice(1); const store_name = name.slice(1);
if (template_scope.names.has(store_name) || scope.has(store_name)) { if (template_scope.names.has(store_name) || scope.has(store_name)) {
@ -113,8 +108,7 @@ export default class Expression {
contextual_dependencies.add(name); contextual_dependencies.add(name);
dependencies.add(name); dependencies.add(name);
} }
} } else if (template_scope.names.has(name)) {
else if (template_scope.names.has(name)) {
expression.uses_context = true; expression.uses_context = true;
contextual_dependencies.add(name); contextual_dependencies.add(name);
const owner = template_scope.get_owner(name); const owner = template_scope.get_owner(name);
@ -124,8 +118,7 @@ export default class Expression {
.get(name) .get(name)
.forEach(/** @param {any} name */ (name) => dependencies.add(name)); .forEach(/** @param {any} name */ (name) => dependencies.add(name));
} }
} } else {
else {
if (!lazy) { if (!lazy) {
dependencies.add(name); dependencies.add(name);
} }
@ -141,27 +134,27 @@ export default class Expression {
if (node.type === 'AssignmentExpression') { if (node.type === 'AssignmentExpression') {
deep = node.left.type === 'MemberExpression'; deep = node.left.type === 'MemberExpression';
names = extract_names(deep ? get_object(node.left) : node.left); names = extract_names(deep ? get_object(node.left) : node.left);
} } else if (node.type === 'UpdateExpression') {
else if (node.type === 'UpdateExpression') {
deep = node.argument.type === 'MemberExpression'; deep = node.argument.type === 'MemberExpression';
names = extract_names(get_object(node.argument)); names = extract_names(get_object(node.argument));
} }
} }
if (names) { if (names) {
names.forEach(/** @param {any} name */ (name) => { names.forEach(
/** @param {any} name */ (name) => {
if (template_scope.names.has(name)) { if (template_scope.names.has(name)) {
if (template_scope.is_const(name)) { if (template_scope.is_const(name)) {
component.error(node, compiler_errors.invalid_const_update(name)); component.error(node, compiler_errors.invalid_const_update(name));
} }
template_scope.dependencies_for_name.get(name).forEach(/** @param {any} name */ (name) => { template_scope.dependencies_for_name.get(name).forEach(
/** @param {any} name */ (name) => {
const variable = component.var_lookup.get(name); const variable = component.var_lookup.get(name);
if (variable) if (variable) variable[deep ? 'mutated' : 'reassigned'] = true;
variable[deep ? 'mutated' : 'reassigned'] = true;
});
const each_block = template_scope.get_owner(name);
( /** @type {import('../EachBlock.js').default} */(each_block)).has_binding = true;
} }
else { );
const each_block = template_scope.get_owner(name);
/** @type {import('../EachBlock.js').default} */ (each_block).has_binding = true;
} else {
component.add_reference(node, name); component.add_reference(node, name);
const variable = component.var_lookup.get(name); const variable = component.var_lookup.get(name);
if (variable) { if (variable) {
@ -177,15 +170,15 @@ export default class Expression {
message: 'You are assigning to a const' message: 'You are assigning to a const'
}); });
} }
} } else if (variable && variable.writable === false && !deep) {
else if (variable && variable.writable === false && !deep) {
component.error(node, { component.error(node, {
code: 'assignment-to-const', code: 'assignment-to-const',
message: 'You are assigning to a const' message: 'You are assigning to a const'
}); });
} }
} }
}); }
);
} }
}, },
@ -201,25 +194,27 @@ export default class Expression {
}); });
} }
dynamic_dependencies() { dynamic_dependencies() {
return Array.from(this.dependencies).filter(/** @param {any} name */ (name) => { return Array.from(this.dependencies).filter(
if (this.template_scope.is_let(name)) /** @param {any} name */ (name) => {
return true; if (this.template_scope.is_let(name)) return true;
if (is_reserved_keyword(name)) if (is_reserved_keyword(name)) return true;
return true;
const variable = this.component.var_lookup.get(name); const variable = this.component.var_lookup.get(name);
return is_dynamic(variable); return is_dynamic(variable);
}); }
);
} }
dynamic_contextual_dependencies() { dynamic_contextual_dependencies() {
return Array.from(this.contextual_dependencies).filter(/** @param {any} name */ (name) => { return Array.from(this.contextual_dependencies).filter(
/** @param {any} name */ (name) => {
return Array.from(this.template_scope.dependencies_for_name.get(name)).some( return Array.from(this.template_scope.dependencies_for_name.get(name)).some(
/** @param {any} variable_name */ /** @param {any} variable_name */
(variable_name) => { (variable_name) => {
const variable = this.component.var_lookup.get(variable_name); const variable = this.component.var_lookup.get(variable_name);
return is_dynamic(variable); return is_dynamic(variable);
}); }
}); );
}
);
} }
// TODO move this into a render-dom wrapper? // TODO move this into a render-dom wrapper?
@ -230,8 +225,7 @@ export default class Expression {
manipulate(block, ctx) { manipulate(block, ctx) {
// TODO ideally we wouldn't end up calling this method // TODO ideally we wouldn't end up calling this method
// multiple times // multiple times
if (this.manipulated) if (this.manipulated) return this.manipulated;
return this.manipulated;
const { component, declarations, scope_map: map, template_scope, owner } = this; const { component, declarations, scope_map: map, template_scope, owner } = this;
let scope = this.scope; let scope = this.scope;
let function_expression; let function_expression;
@ -242,7 +236,6 @@ export default class Expression {
/** @type {Set<string>} */ /** @type {Set<string>} */
let contextual_dependencies; let contextual_dependencies;
const node = walk(this.node, { const node = walk(this.node, {
/** /**
* @param {any} node * @param {any} node
* @param {any} parent * @param {any} parent
@ -257,21 +250,20 @@ export default class Expression {
} }
if (node.type === 'Identifier' && is_reference(node, parent)) { if (node.type === 'Identifier' && is_reference(node, parent)) {
const { name } = flatten_reference(node); const { name } = flatten_reference(node);
if (scope.has(name)) if (scope.has(name)) return;
return;
if (function_expression) { if (function_expression) {
if (template_scope.names.has(name)) { if (template_scope.names.has(name)) {
contextual_dependencies.add(name); contextual_dependencies.add(name);
template_scope.dependencies_for_name.get(name).forEach(/** @param {any} dependency */ (dependency) => { template_scope.dependencies_for_name.get(name).forEach(
/** @param {any} dependency */ (dependency) => {
dependencies.add(dependency); dependencies.add(dependency);
});
} }
else { );
} else {
dependencies.add(name); dependencies.add(name);
component.add_reference(node, name); // TODO is this redundant/misplaced? component.add_reference(node, name); // TODO is this redundant/misplaced?
} }
} } else if (is_contextual(component, template_scope, name)) {
else if (is_contextual(component, template_scope, name)) {
const reference = block.renderer.reference(node, ctx); const reference = block.renderer.reference(node, ctx);
this.replace(reference); this.replace(reference);
} }
@ -294,29 +286,33 @@ export default class Expression {
* @param {import('estree').Node} parent * @param {import('estree').Node} parent
*/ */
leave(node, parent) { leave(node, parent) {
if (map.has(node)) if (map.has(node)) scope = scope.parent;
scope = scope.parent;
if (node === function_expression) { if (node === function_expression) {
const id = component.get_unique_name(sanitize(get_function_name(node, owner))); const id = component.get_unique_name(sanitize(get_function_name(node, owner)));
const declaration = b `const ${id} = ${node}`; const declaration = b`const ${id} = ${node}`;
const extract_functions = () => { const extract_functions = () => {
const deps = Array.from(contextual_dependencies); const deps = Array.from(contextual_dependencies);
const function_expression = /** @type {import('estree').FunctionExpression} */ (node); const function_expression = /** @type {import('estree').FunctionExpression} */ (node);
const has_args = function_expression.params.length > 0; const has_args = function_expression.params.length > 0;
function_expression.params = [ function_expression.params = [
...deps.map(/** @param {any} name */ (name) => ( /** @type {import('estree').Identifier} */({ type: 'Identifier', name }))), ...deps.map(
/** @param {any} name */ (name) =>
/** @type {import('estree').Identifier} */ ({ type: 'Identifier', name })
),
...function_expression.params ...function_expression.params
]; ];
const context_args = deps.map(/** @param {any} name */ (name) => block.renderer.reference(name, ctx)); const context_args = deps.map(
/** @param {any} name */ (name) => block.renderer.reference(name, ctx)
);
component.partly_hoisted.push(declaration); component.partly_hoisted.push(declaration);
block.renderer.add_to_context(id.name); block.renderer.add_to_context(id.name);
const callee = block.renderer.reference(id); const callee = block.renderer.reference(id);
this.replace(/** @type {any} */ (id)); this.replace(/** @type {any} */ (id));
const func_declaration = has_args const func_declaration = has_args
? b `function ${id}(...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 };
@ -326,34 +322,28 @@ export default class Expression {
if (contextual_dependencies.size === 0) { if (contextual_dependencies.size === 0) {
let child_scope = scope; let child_scope = scope;
walk(node, { walk(node, {
/** /**
* @param {import('estree').Node} node * @param {import('estree').Node} node
* @param {any} parent * @param {any} parent
*/ */
enter(node, parent) { enter(node, parent) {
if (map.has(node)) if (map.has(node)) child_scope = map.get(node);
child_scope = map.get(node);
if (node.type === 'Identifier' && is_reference(node, parent)) { if (node.type === 'Identifier' && is_reference(node, parent)) {
if (child_scope.has(node.name)) if (child_scope.has(node.name)) return;
return;
this.replace(block.renderer.reference(node, ctx)); this.replace(block.renderer.reference(node, ctx));
} }
}, },
/** @param {import('estree').Node} node */ /** @param {import('estree').Node} node */
leave(node) { leave(node) {
if (map.has(node)) if (map.has(node)) child_scope = child_scope.parent;
child_scope = child_scope.parent;
} }
}); });
} } else {
else {
const { func_declaration } = extract_functions(); const { func_declaration } = extract_functions();
this.replace(func_declaration[0]); this.replace(func_declaration[0]);
} }
} } else if (dependencies.size === 0 && contextual_dependencies.size === 0) {
else if (dependencies.size === 0 && contextual_dependencies.size === 0) {
// we can hoist this out of the component completely // we can hoist this out of the component completely
component.fully_hoisted.push(declaration); component.fully_hoisted.push(declaration);
this.replace(/** @type {any} */ (id)); this.replace(/** @type {any} */ (id));
@ -363,18 +353,18 @@ export default class Expression {
hoistable: true, hoistable: true,
referenced: true referenced: true
}); });
} } else if (contextual_dependencies.size === 0) {
else if (contextual_dependencies.size === 0) {
// function can be hoisted inside the component init // function can be hoisted inside the component init
component.partly_hoisted.push(declaration); component.partly_hoisted.push(declaration);
block.renderer.add_to_context(id.name); block.renderer.add_to_context(id.name);
this.replace(block.renderer.reference(id)); this.replace(block.renderer.reference(id));
} } else {
else {
// we need a combo block/init recipe // we need a combo block/init recipe
const { deps, func_declaration } = extract_functions(); const { deps, func_declaration } = extract_functions();
if (owner.type === 'Attribute' && owner.parent.name === 'slot') { if (owner.type === 'Attribute' && owner.parent.name === 'slot') {
const dep_scopes = new Set(deps.map(/** @param {any} name */ (name) => template_scope.get_owner(name))); const dep_scopes = new Set(
deps.map(/** @param {any} name */ (name) => template_scope.get_owner(name))
);
// find the nearest scopes // find the nearest scopes
/** @type {import('../interfaces.js').INode} */ /** @type {import('../interfaces.js').INode} */
@ -386,14 +376,12 @@ export default class Expression {
if (node.type === 'InlineComponent' || node.type === 'SlotTemplate') { if (node.type === 'InlineComponent' || node.type === 'SlotTemplate') {
// <Comp let:data /> // <Comp let:data />
this.replace(func_expression); this.replace(func_expression);
} } else {
else {
// {#each}, {#await} // {#each}, {#await}
const func_id = component.get_unique_name(id.name + '_func'); const func_id = component.get_unique_name(id.name + '_func');
block.renderer.add_to_context(func_id.name, true); block.renderer.add_to_context(func_id.name, true);
// rename #ctx -> child_ctx; // rename #ctx -> child_ctx;
walk(func_expression, { walk(func_expression, {
/** @param {import('estree').Node} node */ /** @param {import('estree').Node} node */
enter(node) { enter(node) {
if (node.type === 'Identifier' && node.name === '#ctx') { if (node.type === 'Identifier' && node.name === '#ctx') {
@ -403,7 +391,9 @@ export default class Expression {
}); });
// add to get_xxx_context // add to get_xxx_context
// child_ctx[x] = function () { ... } // child_ctx[x] = function () { ... }
( /** @type {import('../EachBlock.js').default} */(template_scope.get_owner(deps[0]))).contexts.push({ /** @type {import('../EachBlock.js').default} */ (
template_scope.get_owner(deps[0])
).contexts.push({
type: 'DestructuredVariable', type: 'DestructuredVariable',
key: func_id, key: func_id,
modifier: () => func_expression, modifier: () => func_expression,
@ -411,8 +401,7 @@ export default class Expression {
}); });
this.replace(block.renderer.reference(func_id)); this.replace(block.renderer.reference(func_id));
} }
} } else {
else {
declarations.push(func_declaration); declarations.push(func_declaration);
} }
} }
@ -426,8 +415,7 @@ export default class Expression {
if (node.type === 'AssignmentExpression' || node.type === 'UpdateExpression') { if (node.type === 'AssignmentExpression' || node.type === 'UpdateExpression') {
const assignee = node.type === 'AssignmentExpression' ? node.left : node.argument; const assignee = node.type === 'AssignmentExpression' ? node.left : node.argument;
const object_name = get_object(assignee).name; const object_name = get_object(assignee).name;
if (scope.has(object_name)) if (scope.has(object_name)) return;
return;
// normally (`a = 1`, `b.c = 2`), there'll be a single name // normally (`a = 1`, `b.c = 2`), there'll be a single name
// (a or b). In destructuring cases (`[d, e] = [e, d]`) there // (a or b). In destructuring cases (`[d, e] = [e, d]`) there
// may be more, in which case we need to tack the extra ones // may be more, in which case we need to tack the extra ones
@ -436,15 +424,16 @@ export default class Expression {
/** @type {Set<string>} */ /** @type {Set<string>} */
const traced = new Set(); const traced = new Set();
names.forEach(/** @param {any} name */ (name) => { names.forEach(
/** @param {any} name */ (name) => {
const dependencies = template_scope.dependencies_for_name.get(name); const dependencies = template_scope.dependencies_for_name.get(name);
if (dependencies) { if (dependencies) {
dependencies.forEach(/** @param {any} name */ (name) => traced.add(name)); dependencies.forEach(/** @param {any} name */ (name) => traced.add(name));
} } else {
else {
traced.add(name); traced.add(name);
} }
}); }
);
const context = block.bindings.get(object_name); const context = block.bindings.get(object_name);
if (context) { if (context) {
// for `{#each array as item}` // for `{#each array as item}`
@ -456,8 +445,7 @@ export default class Expression {
const replaced = replace_object(assignee, snippet); const replaced = replace_object(assignee, snippet);
if (node.type === 'AssignmentExpression') { if (node.type === 'AssignmentExpression') {
node.left = replaced; node.left = replaced;
} } else {
else {
node.argument = replaced; node.argument = replaced;
} }
contextual_dependencies.add(object.name); contextual_dependencies.add(object.name);
@ -469,9 +457,11 @@ export default class Expression {
}); });
if (declarations.length > 0) { if (declarations.length > 0) {
block.maintain_context = true; block.maintain_context = true;
declarations.forEach(/** @param {any} declaration */ (declaration) => { declarations.forEach(
/** @param {any} declaration */ (declaration) => {
block.chunks.init.push(declaration); block.chunks.init.push(declaration);
}); }
);
} }
return (this.manipulated = /** @type {import('estree').Node} */ (node)); return (this.manipulated = /** @type {import('estree').Node} */ (node));
} }
@ -491,6 +481,4 @@ function get_function_name(_node, parent) {
return 'func'; return 'func';
} }
/** @typedef {INode} Owner */ /** @typedef {INode} Owner */

Loading…
Cancel
Save