docs: add a couple internal JSDocs and cleanup from TS migration (#8940)

pull/8951/head
Ben McCann 2 years ago committed by GitHub
parent 895709c6a2
commit 8601195a85
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -3,16 +3,14 @@ const now = () => performance.now();
/** @param {any} timings */ /** @param {any} timings */
function collapse_timings(timings) { function collapse_timings(timings) {
const result = {}; const result = {};
timings.forEach( timings.forEach((timing) => {
/** @param {any} timing */ (timing) => { result[timing.label] = Object.assign(
result[timing.label] = Object.assign( {
{ total: timing.end - timing.start
total: timing.end - timing.start },
}, timing.children && collapse_timings(timing.children)
timing.children && collapse_timings(timing.children) );
); });
}
);
return result; return result;
} }

File diff suppressed because it is too large Load Diff

@ -23,7 +23,7 @@ export default {
}, },
module_script_variable_reactive_declaration: /** @param {string[]} names */ (names) => ({ module_script_variable_reactive_declaration: /** @param {string[]} names */ (names) => ({
code: 'module-script-reactive-declaration', code: 'module-script-reactive-declaration',
message: `${names.map(/** @param {any} name */ (name) => `"${name}"`).join(', ')} ${ message: `${names.map((name) => `"${name}"`).join(', ')} ${
names.length > 1 ? 'are' : 'is' names.length > 1 ? 'are' : 'is'
} declared in a module script and will not be reactive` } declared in a module script and will not be reactive`
}), }),
@ -175,7 +175,7 @@ export default {
*/ (role, props) => ({ */ (role, props) => ({
code: 'a11y-role-has-required-aria-props', code: 'a11y-role-has-required-aria-props',
message: `A11y: Elements with the ARIA role "${role}" must have the following attributes defined: ${props message: `A11y: Elements with the ARIA role "${role}" must have the following attributes defined: ${props
.map(/** @param {any} name */ (name) => `"${name}"`) .map((name) => `"${name}"`)
.join(', ')}` .join(', ')}`
}), }),
a11y_role_supports_aria_props: /** a11y_role_supports_aria_props: /**

@ -23,18 +23,8 @@ export default function create_module(
exports_from exports_from
) { ) {
const internal_path = `${sveltePath}/internal`; const internal_path = `${sveltePath}/internal`;
helpers.sort( helpers.sort((a, b) => (a.name < b.name ? -1 : 1));
/** globals.sort((a, b) => (a.name < b.name ? -1 : 1));
* @param {any} a
* @param {any} b
*/ (a, b) => (a.name < b.name ? -1 : 1)
);
globals.sort(
/**
* @param {any} a
* @param {any} b
*/ (a, b) => (a.name < b.name ? -1 : 1)
);
return esm( return esm(
program, program,
name, name,
@ -73,19 +63,17 @@ function get_internal_globals(globals, helpers) {
type: 'VariableDeclarator', type: 'VariableDeclarator',
id: { id: {
type: 'ObjectPattern', type: 'ObjectPattern',
properties: globals.map( properties: globals.map((g) => ({
/** @param {any} g */ (g) => ({ type: 'Property',
type: 'Property', method: false,
method: false, shorthand: false,
shorthand: false, computed: false,
computed: false, key: { type: 'Identifier', name: g.name },
key: { type: 'Identifier', name: g.name }, value: g.alias,
value: g.alias, kind: 'init'
kind: 'init' }))
})
)
}, },
init: helpers.find(/** @param {any}params_0 */ ({ name }) => name === 'globals').alias init: helpers.find(({ name }) => name === 'globals').alias
} }
] ]
} }
@ -118,13 +106,11 @@ function esm(
) { ) {
const import_declaration = { const import_declaration = {
type: 'ImportDeclaration', type: 'ImportDeclaration',
specifiers: helpers.map( specifiers: helpers.map((h) => ({
/** @param {any} h */ (h) => ({ type: 'ImportSpecifier',
type: 'ImportSpecifier', local: h.alias,
local: h.alias, imported: { type: 'Identifier', name: h.name }
imported: { type: 'Identifier', name: h.name } })),
})
),
source: { type: 'Literal', value: internal_path } source: { type: 'Literal', value: internal_path }
}; };
const internal_globals = get_internal_globals(globals, helpers); const internal_globals = get_internal_globals(globals, helpers);
@ -142,13 +128,11 @@ function esm(
exports_from.forEach(rewrite_import); exports_from.forEach(rewrite_import);
const exports = module_exports.length > 0 && { const exports = module_exports.length > 0 && {
type: 'ExportNamedDeclaration', type: 'ExportNamedDeclaration',
specifiers: module_exports.map( specifiers: module_exports.map((x) => ({
/** @param {any} x */ (x) => ({ type: 'Specifier',
type: 'Specifier', local: { type: 'Identifier', name: x.name },
local: { type: 'Identifier', name: x.name }, exported: { type: 'Identifier', name: x.as }
exported: { type: 'Identifier', name: x.as } }))
})
)
}; };
program.body = b` program.body = b`
/* ${banner} */ /* ${banner} */

@ -57,16 +57,14 @@ function validate_options(options, warnings) {
} }
const { name, filename, loopGuardTimeout, dev, namespace, css } = options; const { name, filename, loopGuardTimeout, dev, namespace, css } = options;
Object.keys(options).forEach( Object.keys(options).forEach((key) => {
/** @param {any} key */ (key) => { if (!valid_options.includes(key)) {
if (!valid_options.includes(key)) { const match = fuzzymatch(key, valid_options);
const match = fuzzymatch(key, valid_options); let message = `Unrecognized option '${key}'`;
let message = `Unrecognized option '${key}'`; if (match) message += ` (did you mean '${match}'?)`;
if (match) message += ` (did you mean '${match}'?)`; throw new Error(message);
throw new Error(message);
}
} }
); });
if (name && !regex_valid_identifier.test(name)) { if (name && !regex_valid_identifier.test(name)) {
throw new Error(`options.name must be a valid identifier (got '${name}')`); throw new Error(`options.name must be a valid identifier (got '${name}')`);
} }

@ -56,15 +56,13 @@ export default class Attribute extends Node {
this.dependencies = new Set(); this.dependencies = new Set();
this.chunks = this.is_true this.chunks = this.is_true
? [] ? []
: info.value.map( : info.value.map((node) => {
/** @param {any} node */ (node) => { if (node.type === 'Text') return node;
if (node.type === 'Text') return node; this.is_static = false;
this.is_static = false; const expression = new Expression(component, this, scope, node.expression);
const expression = new Expression(component, this, scope, node.expression); add_to_set(this.dependencies, expression.dependencies);
add_to_set(this.dependencies, expression.dependencies); return expression;
return expression; });
}
);
} }
if (this.dependencies.size > 0) { if (this.dependencies.size > 0) {
@ -88,13 +86,11 @@ export default class Attribute extends Node {
/** @type {Set<string>} */ /** @type {Set<string>} */
const dependencies = new Set(); const dependencies = new Set();
this.chunks.forEach( this.chunks.forEach((chunk) => {
/** @param {any} chunk */ (chunk) => { if (chunk.type === 'Expression') {
if (chunk.type === 'Expression') { add_to_set(dependencies, chunk.dynamic_dependencies());
add_to_set(dependencies, chunk.dynamic_dependencies());
}
} }
); });
return Array.from(dependencies); return Array.from(dependencies);
} }
@ -114,12 +110,7 @@ export default class Attribute extends Node {
/** @param {any} chunk */ (chunk) => /** @param {any} chunk */ (chunk) =>
chunk.type === 'Text' ? string_literal(chunk.data) : chunk.manipulate(block) chunk.type === 'Text' ? string_literal(chunk.data) : chunk.manipulate(block)
) )
.reduce( .reduce((lhs, rhs) => x`${lhs} + ${rhs}`);
/**
* @param {any} lhs
* @param {any} rhs
*/ (lhs, rhs) => x`${lhs} + ${rhs}`
);
if (this.chunks[0].type !== 'Text') { if (this.chunks[0].type !== 'Text') {
expression = x`"" + ${expression}`; expression = x`"" + ${expression}`;
} }

@ -54,8 +54,8 @@ export default class Binding extends Node {
this.expression = new Expression(component, this, scope, info.expression); this.expression = new Expression(component, this, scope, info.expression);
this.raw_expression = clone(info.expression); this.raw_expression = clone(info.expression);
const { name } = get_object(this.expression.node); const { name } = get_object(this.expression.node);
this.is_contextual = Array.from(this.expression.references).some( this.is_contextual = Array.from(this.expression.references).some((name) =>
/** @param {any} name */ (name) => scope.names.has(name) scope.names.has(name)
); );
if (this.is_contextual) this.validate_binding_rest_properties(scope); if (this.is_contextual) this.validate_binding_rest_properties(scope);
// make sure we track this as a mutable ref // make sure we track this as a mutable ref
@ -70,14 +70,12 @@ export default class Binding extends Node {
if (scope.is_const(name)) { if (scope.is_const(name)) {
component.error(this, compiler_errors.invalid_binding_const); component.error(this, compiler_errors.invalid_binding_const);
} }
scope.dependencies_for_name.get(name).forEach( scope.dependencies_for_name.get(name).forEach((name) => {
/** @param {any} name */ (name) => { const variable = component.var_lookup.get(name);
const variable = component.var_lookup.get(name); if (variable) {
if (variable) { variable.mutated = true;
variable.mutated = true;
}
} }
); });
} else { } else {
const variable = component.var_lookup.get(name); const variable = component.var_lookup.get(name);
if (!variable || variable.global) { if (!variable || variable.global) {
@ -110,20 +108,18 @@ export default class Binding extends Node {
/** @param {import('./shared/TemplateScope.js').default} scope */ /** @param {import('./shared/TemplateScope.js').default} scope */
validate_binding_rest_properties(scope) { validate_binding_rest_properties(scope) {
this.expression.references.forEach( this.expression.references.forEach((name) => {
/** @param {any} name */ (name) => { const each_block = scope.get_owner(name);
const each_block = scope.get_owner(name); if (each_block && each_block.type === 'EachBlock') {
if (each_block && each_block.type === 'EachBlock') { const rest_node = each_block.context_rest_properties.get(name);
const rest_node = each_block.context_rest_properties.get(name); if (rest_node) {
if (rest_node) { this.component.warn(
this.component.warn( /** @type {any} */ (rest_node),
/** @type {any} */ (rest_node), compiler_warnings.invalid_rest_eachblock_binding(name)
compiler_warnings.invalid_rest_eachblock_binding(name) );
);
}
} }
} }
); });
} }
} }

@ -18,16 +18,14 @@ export default class Body extends Node {
*/ */
constructor(component, parent, scope, info) { constructor(component, parent, scope, info) {
super(component, parent, scope, info); super(component, parent, scope, info);
info.attributes.forEach( info.attributes.forEach((node) => {
/** @param {any} node */ (node) => { if (node.type === 'EventHandler') {
if (node.type === 'EventHandler') { this.handlers.push(new EventHandler(component, this, scope, node));
this.handlers.push(new EventHandler(component, this, scope, node)); } else if (node.type === 'Action') {
} else if (node.type === 'Action') { this.actions.push(new Action(component, this, scope, node));
this.actions.push(new Action(component, this, scope, node)); } else {
} else { // TODO there shouldn't be anything else here...
// TODO there shouldn't be anything else here...
}
} }
); });
} }
} }

@ -19,12 +19,10 @@ export default class CatchBlock extends AbstractBlock {
super(component, parent, scope, info); super(component, parent, scope, info);
this.scope = scope.child(); this.scope = scope.child();
if (parent.catch_node) { if (parent.catch_node) {
parent.catch_contexts.forEach( parent.catch_contexts.forEach((context) => {
/** @param {any} context */ (context) => { if (context.type !== 'DestructuredVariable') return;
if (context.type !== 'DestructuredVariable') return; this.scope.add(context.key.name, parent.expression.dependencies, this);
this.scope.add(context.key.name, parent.expression.dependencies, this); });
}
);
} }
[this.const_tags, this.children] = get_const_tags(info.children, component, this, parent); [this.const_tags, this.children] = get_const_tags(info.children, component, this, parent);
if (!info.skip) { if (!info.skip) {

@ -54,19 +54,16 @@ export default class ConstTag extends Node {
this.node = info; this.node = info;
this.scope = scope; this.scope = scope;
const { assignees, dependencies } = this; const { assignees, dependencies } = this;
extract_identifiers(info.expression.left).forEach( extract_identifiers(info.expression.left).forEach(({ name }) => {
/** @param {any}params_0 */ ({ name }) => { assignees.add(name);
assignees.add(name); const owner = this.scope.get_owner(name);
const owner = this.scope.get_owner(name); if (owner === parent) {
if (owner === parent) { component.error(info, compiler_errors.invalid_const_declaration(name));
component.error(info, compiler_errors.invalid_const_declaration(name));
}
} }
); });
walk(info.expression.right, { walk(info.expression.right, {
/** /**
* @param {any} node * @type {import('estree-walker').SyncHandler}
* @param {any} parent
*/ */
enter(node, parent) { enter(node, parent) {
if ( if (
@ -75,7 +72,7 @@ export default class ConstTag extends Node {
/** @type {import('is-reference').NodeWithPropertyDefinition} */ (parent) /** @type {import('is-reference').NodeWithPropertyDefinition} */ (parent)
) )
) { ) {
const identifier = get_object(/** @type {any} */ (node)); const identifier = get_object(node);
const { name } = identifier; const { name } = identifier;
dependencies.add(name); dependencies.add(name);
} }
@ -92,18 +89,16 @@ export default class ConstTag extends Node {
context_rest_properties: this.context_rest_properties context_rest_properties: this.context_rest_properties
}); });
this.expression = new Expression(this.component, this, this.scope, this.node.expression.right); this.expression = new Expression(this.component, this, this.scope, this.node.expression.right);
this.contexts.forEach( this.contexts.forEach((context) => {
/** @param {any} context */ (context) => { if (context.type !== 'DestructuredVariable') return;
if (context.type !== 'DestructuredVariable') return; const owner = this.scope.get_owner(context.key.name);
const owner = this.scope.get_owner(context.key.name); if (owner && owner.type === 'ConstTag' && owner.parent === this.parent) {
if (owner && owner.type === 'ConstTag' && owner.parent === this.parent) { this.component.error(
this.component.error( this.node,
this.node, compiler_errors.invalid_const_declaration(context.key.name)
compiler_errors.invalid_const_declaration(context.key.name) );
);
}
this.scope.add(context.key.name, this.expression.dependencies, this);
} }
); this.scope.add(context.key.name, this.expression.dependencies, this);
});
} }
} }

@ -28,48 +28,46 @@ export default class Document extends Node {
*/ */
constructor(component, parent, scope, info) { constructor(component, parent, scope, info) {
super(component, parent, scope, info); super(component, parent, scope, info);
info.attributes.forEach( info.attributes.forEach((node) => {
/** @param {any} node */ (node) => { if (node.type === 'EventHandler') {
if (node.type === 'EventHandler') { this.handlers.push(new EventHandler(component, this, scope, node));
this.handlers.push(new EventHandler(component, this, scope, node)); } else if (node.type === 'Binding') {
} else if (node.type === 'Binding') { if (!~valid_bindings.indexOf(node.name)) {
if (!~valid_bindings.indexOf(node.name)) { const match = fuzzymatch(node.name, valid_bindings);
const match = fuzzymatch(node.name, valid_bindings); if (match) {
if (match) { return component.error(
return component.error( node,
node, compiler_errors.invalid_binding_on(
compiler_errors.invalid_binding_on( node.name,
node.name, '<svelte:document>',
'<svelte:document>', ` (did you mean '${match}'?)`
` (did you mean '${match}'?)` )
) );
); } else {
} else { return component.error(
return component.error( node,
node, compiler_errors.invalid_binding_on(
compiler_errors.invalid_binding_on( node.name,
node.name, '<svelte:document>',
'<svelte:document>', ` — valid bindings are ${list(valid_bindings)}`
` — valid bindings are ${list(valid_bindings)}` )
) );
);
}
} }
this.bindings.push(new Binding(component, this, scope, node));
} else if (node.type === 'Action') {
this.actions.push(new Action(component, this, scope, node));
} else {
// TODO there shouldn't be anything else here...
} }
this.bindings.push(new Binding(component, this, scope, node));
} else if (node.type === 'Action') {
this.actions.push(new Action(component, this, scope, node));
} else {
// TODO there shouldn't be anything else here...
} }
); });
this.validate(); this.validate();
} }
/** @private */ /** @private */
validate() { validate() {
const handlers_map = new Set(); const handlers_map = new Set();
this.handlers.forEach(/** @param {any} handler */ (handler) => handlers_map.add(handler.name)); this.handlers.forEach((handler) => handlers_map.add(handler.name));
if (handlers_map.has('mouseenter') || handlers_map.has('mouseleave')) { if (handlers_map.has('mouseenter') || handlers_map.has('mouseleave')) {
this.component.warn(this, compiler_warnings.avoid_mouse_events_on_document); this.component.warn(this, compiler_warnings.avoid_mouse_events_on_document);
} }

@ -71,12 +71,10 @@ export default class EachBlock extends AbstractBlock {
component, component,
context_rest_properties: this.context_rest_properties context_rest_properties: this.context_rest_properties
}); });
this.contexts.forEach( this.contexts.forEach((context) => {
/** @param {any} context */ (context) => { if (context.type !== 'DestructuredVariable') return;
if (context.type !== 'DestructuredVariable') return; this.scope.add(context.key.name, this.expression.dependencies, this);
this.scope.add(context.key.name, this.expression.dependencies, this); });
}
);
if (this.index) { if (this.index) {
// index can only change if this is a keyed each block // index can only change if this is a keyed each block
const dependencies = info.key ? this.expression.dependencies : new Set([]); const dependencies = info.key ? this.expression.dependencies : new Set([]);
@ -86,13 +84,10 @@ export default class EachBlock extends AbstractBlock {
this.has_animation = false; this.has_animation = false;
[this.const_tags, this.children] = get_const_tags(info.children, component, this, this); [this.const_tags, this.children] = get_const_tags(info.children, component, this, this);
if (this.has_animation) { if (this.has_animation) {
this.children = this.children.filter( this.children = this.children.filter((child) => !isEmptyNode(child) && !isCommentNode(child));
/** @param {any} child */ (child) => !isEmptyNode(child) && !isCommentNode(child)
);
if (this.children.length !== 1) { if (this.children.length !== 1) {
const child = this.children.find( const child = this.children.find(
/** @param {any} child */ (child) => (child) => !!(/** @type {import('./Element.js').default} */ (child).animation)
!!(/** @type {import('./Element.js').default} */ (child).animation)
); );
component.error( component.error(
/** @type {import('./Element.js').default} */ (child).animation, /** @type {import('./Element.js').default} */ (child).animation,

File diff suppressed because it is too large Load Diff

@ -48,7 +48,6 @@ export default class EventHandler extends Node {
if (node.type === 'VariableDeclaration') { if (node.type === 'VariableDeclaration') {
// for `const handleClick = () => {...}`, we want the [arrow] function expression node // for `const handleClick = () => {...}`, we want the [arrow] function expression node
const declarator = node.declarations.find( const declarator = node.declarations.find(
/** @param {any} d */
(d) => /** @type {import('estree').Identifier} */ (d.id).name === info.expression.name (d) => /** @type {import('estree').Identifier} */ (d.id).name === info.expression.name
); );
node = declarator && declarator.init; node = declarator && declarator.init;

@ -29,11 +29,9 @@ export default class Head extends Node {
component, component,
parent, parent,
scope, scope,
info.children.filter( info.children.filter((child) => {
/** @param {any} child */ (child) => { return child.type !== 'Text' || regex_non_whitespace_character.test(child.data);
return child.type !== 'Text' || regex_non_whitespace_character.test(child.data); })
}
)
); );
if (this.children.length > 0) { if (this.children.length > 0) {
this.id = `svelte-${hash(this.component.source.slice(this.start, this.end))}`; this.id = `svelte-${hash(this.component.source.slice(this.start, this.end))}`;

@ -101,17 +101,13 @@ export default class InlineComponent extends Node {
this.scope = scope; this.scope = scope;
this.handlers.forEach( this.handlers.forEach((handler) => {
/** @param {any} handler */ (handler) => { handler.modifiers.forEach((modifier) => {
handler.modifiers.forEach( if (modifier !== 'once') {
/** @param {any} modifier */ (modifier) => { return component.error(handler, compiler_errors.invalid_event_modifier_component);
if (modifier !== 'once') { }
return component.error(handler, compiler_errors.invalid_event_modifier_component); });
} });
}
);
}
);
const children = []; const children = [];
for (let i = info.children.length - 1; i >= 0; i--) { for (let i = info.children.length - 1; i >= 0; i--) {
const child = info.children[i]; const child = info.children[i];
@ -120,9 +116,7 @@ export default class InlineComponent extends Node {
info.children.splice(i, 1); info.children.splice(i, 1);
} else if ( } else if (
(child.type === 'Element' || child.type === 'InlineComponent' || child.type === 'Slot') && (child.type === 'Element' || child.type === 'InlineComponent' || child.type === 'Slot') &&
child.attributes.find( child.attributes.find((attribute) => attribute.name === 'slot')
/** @param {any} attribute */ (attribute) => attribute.name === 'slot'
)
) { ) {
const slot_template = { const slot_template = {
start: child.start, start: child.start,
@ -156,7 +150,7 @@ export default class InlineComponent extends Node {
children[children.length - 1].children.unshift(child); children[children.length - 1].children.unshift(child);
} }
} }
if (info.children.some(/** @param {any} node */ (node) => not_whitespace_text(node))) { if (info.children.some((node) => not_whitespace_text(node))) {
children.push({ children.push({
start: info.start, start: info.start,
end: info.end, end: info.end,
@ -182,9 +176,7 @@ export default class InlineComponent extends Node {
} }
get slot_template_name() { get slot_template_name() {
return /** @type {string} */ ( return /** @type {string} */ (
this.attributes this.attributes.find((attribute) => attribute.name === 'slot').get_static_value()
.find(/** @param {any} attribute */ (attribute) => attribute.name === 'slot')
.get_static_value()
); );
} }
} }

@ -22,23 +22,21 @@ export default class Slot extends Element {
*/ */
constructor(component, parent, scope, info) { constructor(component, parent, scope, info) {
super(component, parent, scope, info); super(component, parent, scope, info);
info.attributes.forEach( info.attributes.forEach((attr) => {
/** @param {any} attr */ (attr) => { if (attr.type !== 'Attribute' && attr.type !== 'Spread') {
if (attr.type !== 'Attribute' && attr.type !== 'Spread') { return component.error(attr, compiler_errors.invalid_slot_directive);
return component.error(attr, compiler_errors.invalid_slot_directive); }
if (attr.name === 'name') {
if (attr.value.length !== 1 || attr.value[0].type !== 'Text') {
return component.error(attr, compiler_errors.dynamic_slot_name);
} }
if (attr.name === 'name') { this.slot_name = attr.value[0].data;
if (attr.value.length !== 1 || attr.value[0].type !== 'Text') { if (this.slot_name === 'default') {
return component.error(attr, compiler_errors.dynamic_slot_name); return component.error(attr, compiler_errors.invalid_slot_name);
}
this.slot_name = attr.value[0].data;
if (this.slot_name === 'default') {
return component.error(attr, compiler_errors.invalid_slot_name);
}
} }
this.values.set(attr.name, new Attribute(component, this, scope, attr));
} }
); this.values.set(attr.name, new Attribute(component, this, scope, attr));
});
if (!this.slot_name) this.slot_name = 'default'; if (!this.slot_name) this.slot_name = 'default';
component.slots.set(this.slot_name, this); component.slots.set(this.slot_name, this);

@ -34,40 +34,36 @@ export default class SlotTemplate extends Node {
super(component, parent, scope, info); super(component, parent, scope, info);
this.validate_slot_template_placement(); this.validate_slot_template_placement();
scope = scope.child(); scope = scope.child();
info.attributes.forEach( info.attributes.forEach((node) => {
/** @param {any} node */ (node) => { switch (node.type) {
switch (node.type) { case 'Let': {
case 'Let': { const l = new Let(component, this, scope, node);
const l = new Let(component, this, scope, node); this.lets.push(l);
this.lets.push(l); const dependencies = new Set([l.name.name]);
const dependencies = new Set([l.name.name]); l.names.forEach((name) => {
l.names.forEach( scope.add(name, dependencies, this);
/** @param {any} name */ (name) => { });
scope.add(name, dependencies, this); break;
} }
); case 'Attribute': {
break; if (node.name === 'slot') {
} this.slot_attribute = new Attribute(component, this, scope, node);
case 'Attribute': { if (!this.slot_attribute.is_static) {
if (node.name === 'slot') { return component.error(node, compiler_errors.invalid_slot_attribute);
this.slot_attribute = new Attribute(component, this, scope, node);
if (!this.slot_attribute.is_static) {
return component.error(node, compiler_errors.invalid_slot_attribute);
}
const value = this.slot_attribute.get_static_value();
if (typeof value === 'boolean') {
return component.error(node, compiler_errors.invalid_slot_attribute_value_missing);
}
this.slot_template_name = /** @type {string} */ (value);
break;
} }
throw new Error(`Invalid attribute '${node.name}' in <svelte:fragment>`); const value = this.slot_attribute.get_static_value();
if (typeof value === 'boolean') {
return component.error(node, compiler_errors.invalid_slot_attribute_value_missing);
}
this.slot_template_name = /** @type {string} */ (value);
break;
} }
default: throw new Error(`Invalid attribute '${node.name}' in <svelte:fragment>`);
throw new Error(`Not implemented: ${node.type}`);
} }
default:
throw new Error(`Not implemented: ${node.type}`);
} }
); });
this.scope = scope; this.scope = scope;
[this.const_tags, this.children] = get_const_tags(info.children, component, this, this); [this.const_tags, this.children] = get_const_tags(info.children, component, this, this);
} }

@ -43,12 +43,12 @@ export default class StyleDirective extends Node {
if (info.value === true || (info.value.length === 1 && info.value[0].type === 'MustacheTag')) { if (info.value === true || (info.value.length === 1 && info.value[0].type === 'MustacheTag')) {
const identifier = const identifier =
info.value === true info.value === true
? /** @type {any} */ ({ ? {
type: 'Identifier', type: 'Identifier',
start: info.end - info.name.length, start: info.end - info.name.length,
end: info.end, end: info.end,
name: info.name name: info.name
}) }
: info.value[0].expression; : info.value[0].expression;
this.expression = new Expression(component, this, scope, identifier); this.expression = new Expression(component, this, scope, identifier);
this.should_cache = false; this.should_cache = false;

@ -19,12 +19,10 @@ export default class ThenBlock extends AbstractBlock {
super(component, parent, scope, info); super(component, parent, scope, info);
this.scope = scope.child(); this.scope = scope.child();
if (parent.then_node) { if (parent.then_node) {
parent.then_contexts.forEach( parent.then_contexts.forEach((context) => {
/** @param {any} context */ (context) => { if (context.type !== 'DestructuredVariable') return;
if (context.type !== 'DestructuredVariable') return; this.scope.add(context.key.name, parent.expression.dependencies, this);
this.scope.add(context.key.name, parent.expression.dependencies, this); });
}
);
} }
[this.const_tags, this.children] = get_const_tags(info.children, component, this, parent); [this.const_tags, this.children] = get_const_tags(info.children, component, this, parent);
if (!info.skip) { if (!info.skip) {

@ -23,13 +23,11 @@ export default class Title extends Node {
component.error(info.attributes[0], compiler_errors.illegal_attribute_title); component.error(info.attributes[0], compiler_errors.illegal_attribute_title);
return; return;
} }
info.children.forEach( info.children.forEach((child) => {
/** @param {any} child */ (child) => { if (child.type !== 'Text' && child.type !== 'MustacheTag') {
if (child.type !== 'Text' && child.type !== 'MustacheTag') { return component.error(child, compiler_errors.illegal_structure_title);
return component.error(child, compiler_errors.illegal_structure_title);
}
} }
); });
this.should_cache = this.should_cache =
info.children.length === 1 info.children.length === 1
? info.children[0].type !== 'Identifier' || scope.names.has(info.children[0].name) ? info.children[0].type !== 'Identifier' || scope.names.has(info.children[0].name)

@ -37,50 +37,48 @@ export default class Window extends Node {
*/ */
constructor(component, parent, scope, info) { constructor(component, parent, scope, info) {
super(component, parent, scope, info); super(component, parent, scope, info);
info.attributes.forEach( info.attributes.forEach((node) => {
/** @param {any} node */ (node) => { if (node.type === 'EventHandler') {
if (node.type === 'EventHandler') { this.handlers.push(new EventHandler(component, this, scope, node));
this.handlers.push(new EventHandler(component, this, scope, node)); } else if (node.type === 'Binding') {
} else if (node.type === 'Binding') { if (node.expression.type !== 'Identifier') {
if (node.expression.type !== 'Identifier') { const { parts } = flatten_reference(node.expression);
const { parts } = flatten_reference(node.expression); // TODO is this constraint necessary?
// TODO is this constraint necessary? return component.error(node.expression, compiler_errors.invalid_binding_window(parts));
return component.error(node.expression, compiler_errors.invalid_binding_window(parts)); }
} if (!~valid_bindings.indexOf(node.name)) {
if (!~valid_bindings.indexOf(node.name)) { const match =
const match = node.name === 'width'
node.name === 'width' ? 'innerWidth'
? 'innerWidth' : node.name === 'height'
: node.name === 'height' ? 'innerHeight'
? 'innerHeight' : fuzzymatch(node.name, valid_bindings);
: fuzzymatch(node.name, valid_bindings); if (match) {
if (match) { return component.error(
return component.error( node,
node, compiler_errors.invalid_binding_on(
compiler_errors.invalid_binding_on( node.name,
node.name, '<svelte:window>',
'<svelte:window>', ` (did you mean '${match}'?)`
` (did you mean '${match}'?)` )
) );
); } else {
} else { return component.error(
return component.error( node,
node, compiler_errors.invalid_binding_on(
compiler_errors.invalid_binding_on( node.name,
node.name, '<svelte:window>',
'<svelte:window>', ` — valid bindings are ${list(valid_bindings)}`
` — valid bindings are ${list(valid_bindings)}` )
) );
);
}
} }
this.bindings.push(new Binding(component, this, scope, node));
} else if (node.type === 'Action') {
this.actions.push(new Action(component, this, scope, node));
} else {
// TODO there shouldn't be anything else here...
} }
this.bindings.push(new Binding(component, this, scope, node));
} else if (node.type === 'Action') {
this.actions.push(new Action(component, this, scope, node));
} else {
// TODO there shouldn't be anything else here...
} }
); });
} }
} }

@ -31,10 +31,16 @@ export default class Expression {
/** @type {Set<string>} */ /** @type {Set<string>} */
references = new Set(); references = new Set();
/** @type {Set<string>} */ /**
* Dependencies declared in the script block
* @type {Set<string>}
*/
dependencies = new Set(); dependencies = new Set();
/** @type {Set<string>} */ /**
* Dependencies declared in the HTML-like template section
* @type {Set<string>}
*/
contextual_dependencies = new Set(); contextual_dependencies = new Set();
/** @type {import('./TemplateScope.js').default} */ /** @type {import('./TemplateScope.js').default} */
@ -82,12 +88,12 @@ export default class Expression {
walk(info, { walk(info, {
/** /**
* @param {any} node * @param {any} node
* @param {any} parent * @param {import('estree').Node} parent
* @param {string} key * @param {string} key
*/ */
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) return; if (key === 'key' && /** @type {import('estree').Property} */ (parent).shorthand) return;
// don't manipulate `import.meta`, `new.target` // don't manipulate `import.meta`, `new.target`
if (node.type === 'MetaProperty') return this.skip(); if (node.type === 'MetaProperty') return this.skip();
if (map.has(node)) { if (map.has(node)) {
@ -119,7 +125,7 @@ export default class Expression {
if (!lazy || is_index) { if (!lazy || is_index) {
template_scope.dependencies_for_name template_scope.dependencies_for_name
.get(name) .get(name)
.forEach(/** @param {any} name */ (name) => dependencies.add(name)); .forEach((name) => dependencies.add(name));
} }
} else { } else {
if (!lazy) { if (!lazy) {
@ -143,49 +149,48 @@ export default class Expression {
} }
} }
if (names) { if (names) {
names.forEach( names.forEach((name) => {
/** @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((name) => {
template_scope.dependencies_for_name.get(name).forEach(
/** @param {any} name */ (name) => {
const variable = component.var_lookup.get(name);
if (variable) variable[deep ? 'mutated' : 'reassigned'] = true;
}
);
const each_block = template_scope.get_owner(name);
/** @type {import('../EachBlock.js').default} */ (each_block).has_binding = true;
} else {
component.add_reference(node, 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 {
component.add_reference(node, name);
const variable = component.var_lookup.get(name);
if (variable) {
variable[deep ? 'mutated' : 'reassigned'] = true;
}
/** @type {any} */ const declaration = scope.find_owner(name)?.declarations.get(name);
const declaration = scope.find_owner(name)?.declarations.get(name); if (declaration) {
if (declaration) { if (
if (declaration.kind === 'const' && !deep) { /** @type {import('estree').VariableDeclaration} */ (declaration).kind ===
component.error(node, { 'const' &&
code: 'assignment-to-const', !deep
message: 'You are assigning to a const' ) {
});
}
} 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'
}); });
} }
} else if (variable && variable.writable === false && !deep) {
component.error(node, {
code: 'assignment-to-const',
message: 'You are assigning to a const'
});
} }
} }
); });
} }
}, },
/** @param {import('estree').Node} node */ /** @type {import('estree-walker').SyncHandler} */
leave(node) { leave(node) {
if (map.has(node)) { if (map.has(node)) {
scope = scope.parent; scope = scope.parent;
@ -197,27 +202,22 @@ export default class Expression {
}); });
} }
dynamic_dependencies() { dynamic_dependencies() {
return Array.from(this.dependencies).filter( return Array.from(this.dependencies).filter((name) => {
/** @param {any} name */ (name) => { if (this.template_scope.is_let(name)) return true;
if (this.template_scope.is_let(name)) return true; if (is_reserved_keyword(name)) return true;
if (is_reserved_keyword(name)) 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( return Array.from(this.contextual_dependencies).filter((name) => {
/** @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( (variable_name) => {
/** @param {any} variable_name */ const variable = this.component.var_lookup.get(variable_name);
(variable_name) => { return is_dynamic(variable);
const variable = this.component.var_lookup.get(variable_name); }
return is_dynamic(variable); );
} });
);
}
);
} }
// TODO move this into a render-dom wrapper? // TODO move this into a render-dom wrapper?
@ -239,10 +239,7 @@ 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, {
/** /** @type {import('estree-walker').SyncHandler} */
* @param {any} node
* @param {any} parent
*/
enter(node, parent) { enter(node, parent) {
if (node.type === 'Property' && node.shorthand) { if (node.type === 'Property' && node.shorthand) {
node.value = clone(node.value); node.value = clone(node.value);
@ -257,11 +254,9 @@ export default class Expression {
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( template_scope.dependencies_for_name.get(name).forEach((dependency) => {
/** @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?
@ -284,10 +279,7 @@ export default class Expression {
} }
}, },
/** /** @type {import('estree-walker').SyncHandler} */
* @param {import('estree').Node} node
* @param {import('estree').Node} parent
*/
leave(node, parent) { leave(node, parent) {
if (map.has(node)) scope = scope.parent; if (map.has(node)) scope = scope.parent;
if (node === function_expression) { if (node === function_expression) {
@ -299,18 +291,15 @@ export default class Expression {
const has_args = function_expression.params.length > 0; const has_args = function_expression.params.length > 0;
function_expression.params = [ function_expression.params = [
...deps.map( ...deps.map(
/** @param {any} name */ (name) => (name) => /** @type {import('estree').Identifier} */ ({ type: 'Identifier', name })
/** @type {import('estree').Identifier} */ ({ type: 'Identifier', name })
), ),
...function_expression.params ...function_expression.params
]; ];
const context_args = deps.map( const context_args = deps.map((name) => block.renderer.reference(name, ctx));
/** @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(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);
@ -325,10 +314,7 @@ 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, {
/** /** @type {import('estree-walker').SyncHandler} */
* @param {import('estree').Node} node
* @param {any} parent
*/
enter(node, parent) { enter(node, parent) {
if (map.has(node)) child_scope = map.get(node); if (map.has(node)) child_scope = map.get(node);
if (node.type === 'Identifier' && is_reference(node, parent)) { if (node.type === 'Identifier' && is_reference(node, parent)) {
@ -349,7 +335,7 @@ export default class Expression {
} 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(id);
component.add_var(node, { component.add_var(node, {
name: id.name, name: id.name,
internal: true, internal: true,
@ -366,9 +352,7 @@ export default class Expression {
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') {
/** @type {Set<import('../interfaces.js').INode>} */ /** @type {Set<import('../interfaces.js').INode>} */
const dep_scopes = new Set( const dep_scopes = new Set(deps.map((name) => template_scope.get_owner(name)));
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} */
@ -402,7 +386,7 @@ export default class Expression {
type: 'DestructuredVariable', type: 'DestructuredVariable',
key: func_id, key: func_id,
modifier: () => func_expression, modifier: () => func_expression,
default_modifier: /** @param {any} node */ (node) => node default_modifier: (node) => node
}); });
this.replace(block.renderer.reference(func_id)); this.replace(block.renderer.reference(func_id));
} }
@ -429,16 +413,14 @@ export default class Expression {
/** @type {Set<string>} */ /** @type {Set<string>} */
const traced = new Set(); const traced = new Set();
names.forEach( names.forEach((name) => {
/** @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((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}`
@ -463,19 +445,17 @@ export default class Expression {
if (declarations.length > 0) { if (declarations.length > 0) {
block.maintain_context = true; block.maintain_context = true;
declarations.forEach( declarations.forEach((declaration) => {
/** @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));
} }
} }
/** /**
* @param {any} _node * @param {import('estree').Node} _node
* @param {any} parent * @param {import('../interfaces.js').INode} parent
*/ */
function get_function_name(_node, parent) { function get_function_name(_node, parent) {
if (parent.type === 'EventHandler') { if (parent.type === 'EventHandler') {

@ -1,3 +1,4 @@
/** The scope of constructs within the Svelte template */
export default class TemplateScope { export default class TemplateScope {
/** /**
* @typedef {import('../EachBlock').default * @typedef {import('../EachBlock').default

@ -23,11 +23,9 @@ export default function get_const_tags(children, component, node, parent) {
others.push(child); others.push(child);
} }
} }
const consts_nodes = const_tags.map( const consts_nodes = const_tags.map((tag) => new ConstTag(component, node, node.scope, tag));
/** @param {any} tag */ (tag) => new ConstTag(component, node, node.scope, tag)
);
const sorted_consts_nodes = sort_consts_nodes(consts_nodes, component); const sorted_consts_nodes = sort_consts_nodes(consts_nodes, component);
sorted_consts_nodes.forEach(/** @param {any} node */ (node) => node.parse_expression()); sorted_consts_nodes.forEach((node) => node.parse_expression());
const children_nodes = map_children(component, parent, node.scope, others); const children_nodes = map_children(component, parent, node.scope, others);
return [ return [
sorted_consts_nodes, sorted_consts_nodes,
@ -46,49 +44,33 @@ function sort_consts_nodes(consts_nodes, component) {
const sorted_consts_nodes = []; const sorted_consts_nodes = [];
/** @type {ConstNode[]} */ /** @type {ConstNode[]} */
const unsorted_consts_nodes = consts_nodes.map( const unsorted_consts_nodes = consts_nodes.map((node) => {
/** @param {any} node */ (node) => { return {
return { assignees: node.assignees,
assignees: node.assignees, dependencies: node.dependencies,
dependencies: node.dependencies, node
node };
}; });
}
);
const lookup = new Map(); const lookup = new Map();
unsorted_consts_nodes.forEach( unsorted_consts_nodes.forEach((node) => {
/** @param {any} node */ (node) => { node.assignees.forEach((name) => {
node.assignees.forEach( if (!lookup.has(name)) {
/** @param {any} name */ (name) => { lookup.set(name, []);
if (!lookup.has(name)) { }
lookup.set(name, []); lookup.get(name).push(node);
} });
lookup.get(name).push(node); });
}
);
}
);
const cycle = check_graph_for_cycles( const cycle = check_graph_for_cycles(
unsorted_consts_nodes.reduce( unsorted_consts_nodes.reduce((acc, node) => {
/** node.assignees.forEach((v) => {
* @param {any} acc node.dependencies.forEach((w) => {
* @param {any} node if (!node.assignees.has(w)) {
*/ (acc, node) => { acc.push([v, w]);
node.assignees.forEach(
/** @param {any} v */ (v) => {
node.dependencies.forEach(
/** @param {any} w */ (w) => {
if (!node.assignees.has(w)) {
acc.push([v, w]);
}
}
);
} }
); });
return acc; });
}, return acc;
[] }, [])
)
); );
if (cycle && cycle.length) { if (cycle && cycle.length) {
const nodeList = lookup.get(cycle[0]); const nodeList = lookup.get(cycle[0]);
@ -99,17 +81,15 @@ function sort_consts_nodes(consts_nodes, component) {
/** @param {ConstNode} node */ /** @param {ConstNode} node */
const add_node = (node) => { const add_node = (node) => {
if (sorted_consts_nodes.includes(node)) return; if (sorted_consts_nodes.includes(node)) return;
node.dependencies.forEach( node.dependencies.forEach((name) => {
/** @param {any} name */ (name) => { if (node.assignees.has(name)) return;
if (node.assignees.has(name)) return; const earlier_nodes = lookup.get(name);
const earlier_nodes = lookup.get(name); if (earlier_nodes) {
if (earlier_nodes) { earlier_nodes.forEach(add_node);
earlier_nodes.forEach(add_node);
}
} }
); });
sorted_consts_nodes.push(node); sorted_consts_nodes.push(node);
}; };
unsorted_consts_nodes.forEach(add_node); unsorted_consts_nodes.forEach(add_node);
return sorted_consts_nodes.map(/** @param {any} node */ (node) => node.node); return sorted_consts_nodes.map((node) => node.node);
} }

@ -79,20 +79,18 @@ function get_constructor(type) {
export default function map_children(component, parent, scope, children) { export default function map_children(component, parent, scope, children) {
let last = null; let last = null;
let ignores = []; let ignores = [];
return children.map( return children.map((child) => {
/** @param {any} child */ (child) => { const constructor = get_constructor(child.type);
const constructor = get_constructor(child.type); const use_ignores = child.type !== 'Text' && child.type !== 'Comment' && ignores.length;
const use_ignores = child.type !== 'Text' && child.type !== 'Comment' && ignores.length; if (use_ignores) component.push_ignores(ignores);
if (use_ignores) component.push_ignores(ignores); const node = new constructor(component, parent, scope, child);
const node = new constructor(component, parent, scope, child); if (use_ignores) component.pop_ignores(), (ignores = []);
if (use_ignores) component.pop_ignores(), (ignores = []); if (node.type === 'Comment' && node.ignores.length) {
if (node.type === 'Comment' && node.ignores.length) { push_array(ignores, node.ignores);
push_array(ignores, node.ignores);
}
if (last) last.next = node;
node.prev = last;
last = node;
return node;
} }
); if (last) last.next = node;
node.prev = last;
last = node;
return node;
});
} }

@ -73,9 +73,7 @@ export default function read_context(parser) {
space_with_newline = space_with_newline =
space_with_newline.slice(0, first_space) + space_with_newline.slice(first_space + 1); space_with_newline.slice(0, first_space) + space_with_newline.slice(first_space + 1);
return /** @type {any} */ ( return parse_expression_at(`${space_with_newline}(${pattern_string} = 1)`, start - 1).left;
parse_expression_at(`${space_with_newline}(${pattern_string} = 1)`, start - 1)
).left;
} catch (error) { } catch (error) {
parser.acorn_error(error); parser.acorn_error(error);
} }

@ -588,14 +588,12 @@ export function init_binding_group_dynamic(group, indexes) {
}; };
} }
/** /** @returns {number} */
* @returns {number} */
export function to_number(value) { export function to_number(value) {
return value === '' ? null : +value; return value === '' ? null : +value;
} }
/** /** @returns {any[]} */
* @returns {any[]} */
export function time_ranges_to_array(ranges) { export function time_ranges_to_array(ranges) {
const array = []; const array = [];
for (let i = 0; i < ranges.length; i += 1) { for (let i = 0; i < ranges.length; i += 1) {

Loading…
Cancel
Save