Comparing version 5.0.0-next.200 to 5.0.0-next.201
@@ -5,3 +5,3 @@ { | ||
"license": "MIT", | ||
"version": "5.0.0-next.200", | ||
"version": "5.0.0-next.201", | ||
"type": "module", | ||
@@ -8,0 +8,0 @@ "types": "./types/index.d.ts", |
@@ -981,3 +981,3 @@ /* This file is generated by scripts/process-messages/index.js. Do not edit! */ | ||
/** | ||
* %thing% is invalid inside <%parent%> | ||
* %thing% is invalid inside `<%parent%>` | ||
* @param {null | number | NodeLike} node | ||
@@ -989,3 +989,3 @@ * @param {string} thing | ||
export function node_invalid_placement(node, thing, parent) { | ||
e(node, "node_invalid_placement", `${thing} is invalid inside <${parent}>`); | ||
e(node, "node_invalid_placement", `${thing} is invalid inside \`<${parent}>\``); | ||
} | ||
@@ -992,0 +992,0 @@ |
@@ -8,3 +8,3 @@ /** @import { Expression } from 'estree' */ | ||
import read_style from '../read/style.js'; | ||
import { closing_tag_omitted, decode_character_references } from '../utils/html.js'; | ||
import { decode_character_references } from '../utils/html.js'; | ||
import * as e from '../../../errors.js'; | ||
@@ -15,2 +15,3 @@ import * as w from '../../../warnings.js'; | ||
import { get_attribute_expression, is_expression_attribute } from '../../../utils/ast.js'; | ||
import { closing_tag_omitted } from '../../../../html-tree-validation.js'; | ||
@@ -17,0 +18,0 @@ // eslint-disable-next-line no-useless-escape |
@@ -1,2 +0,1 @@ | ||
import { interactive_elements } from '../../../../constants.js'; | ||
import entities from './entities.js'; | ||
@@ -122,46 +121,1 @@ | ||
} | ||
// based on http://developers.whatwg.org/syntax.html#syntax-tag-omission | ||
/** @type {Record<string, Set<string>>} */ | ||
const disallowed_contents = { | ||
li: new Set(['li']), | ||
dt: new Set(['dt', 'dd']), | ||
dd: new Set(['dt', 'dd']), | ||
p: new Set( | ||
'address article aside blockquote div dl fieldset footer form h1 h2 h3 h4 h5 h6 header hgroup hr main menu nav ol p pre section table ul'.split( | ||
' ' | ||
) | ||
), | ||
rt: new Set(['rt', 'rp']), | ||
rp: new Set(['rt', 'rp']), | ||
optgroup: new Set(['optgroup']), | ||
option: new Set(['option', 'optgroup']), | ||
thead: new Set(['tbody', 'tfoot']), | ||
tbody: new Set(['tbody', 'tfoot']), | ||
tfoot: new Set(['tbody']), | ||
tr: new Set(['tr', 'tbody']), | ||
td: new Set(['td', 'th', 'tr']), | ||
th: new Set(['td', 'th', 'tr']) | ||
}; | ||
for (const interactive_element of interactive_elements) { | ||
disallowed_contents[interactive_element] = interactive_elements; | ||
} | ||
// can this be a child of the parent element, or does it implicitly | ||
// close it, like `<li>one<li>two`? | ||
/** | ||
* @param {string} current | ||
* @param {string} [next] | ||
*/ | ||
export function closing_tag_omitted(current, next) { | ||
if (disallowed_contents[current]) { | ||
if (!next || disallowed_contents[current].has(next)) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} |
@@ -1550,2 +1550,9 @@ /** @import { ArrowFunctionExpression, CallExpression, Expression, FunctionDeclaration, FunctionExpression, Identifier, LabeledStatement, Literal, Node, Program, Super } from 'estree' */ | ||
context.next({ ...context.state, render_tag: node }); | ||
}, | ||
EachBlock(node) { | ||
if (node.key) { | ||
// treat `{#each items as item, i (i)}` as a normal indexed block, everything else as keyed | ||
node.metadata.keyed = | ||
node.key.type !== 'Identifier' || !node.index || node.key.name !== node.index; | ||
} | ||
} | ||
@@ -1552,0 +1559,0 @@ }; |
@@ -6,7 +6,2 @@ /** @import { AssignmentExpression, CallExpression, Expression, Identifier, Node, Pattern, PrivateIdentifier, Super, UpdateExpression, VariableDeclarator } from 'estree' */ | ||
import is_reference from 'is-reference'; | ||
import { | ||
disallowed_paragraph_contents, | ||
interactive_elements, | ||
is_tag_valid_with_parent | ||
} from '../../../constants.js'; | ||
import * as e from '../../errors.js'; | ||
@@ -41,2 +36,6 @@ import { | ||
import { a11y_validators } from './a11y.js'; | ||
import { | ||
is_tag_valid_with_ancestor, | ||
is_tag_valid_with_parent | ||
} from '../../../html-tree-validation.js'; | ||
@@ -391,16 +390,14 @@ /** | ||
if ( | ||
assignee.type === 'Identifier' && | ||
node.name !== 'this' // bind:this also works for regular variables | ||
) { | ||
if (assignee.type === 'Identifier') { | ||
// reassignment | ||
if ( | ||
!binding || | ||
(binding.kind !== 'state' && | ||
binding.kind !== 'frozen_state' && | ||
binding.kind !== 'prop' && | ||
binding.kind !== 'bindable_prop' && | ||
binding.kind !== 'each' && | ||
binding.kind !== 'store_sub' && | ||
!binding.mutated) | ||
node.name !== 'this' && // bind:this also works for regular variables | ||
(!binding || | ||
(binding.kind !== 'state' && | ||
binding.kind !== 'frozen_state' && | ||
binding.kind !== 'prop' && | ||
binding.kind !== 'bindable_prop' && | ||
binding.kind !== 'each' && | ||
binding.kind !== 'store_sub' && | ||
!binding.mutated)) | ||
) { | ||
@@ -410,11 +407,11 @@ e.bind_invalid_value(node.expression); | ||
if (binding.kind === 'derived') { | ||
if (binding?.kind === 'derived') { | ||
e.constant_binding(node.expression, 'derived state'); | ||
} | ||
if (context.state.analysis.runes && binding.kind === 'each') { | ||
if (context.state.analysis.runes && binding?.kind === 'each') { | ||
e.each_item_invalid_assignment(node); | ||
} | ||
if (binding.kind === 'snippet') { | ||
if (binding?.kind === 'snippet') { | ||
e.snippet_parameter_assignment(node); | ||
@@ -609,36 +606,53 @@ } | ||
if (context.state.parent_element) { | ||
if (!is_tag_valid_with_parent(node.name, context.state.parent_element)) { | ||
e.node_invalid_placement(node, `<${node.name}>`, context.state.parent_element); | ||
} | ||
} | ||
let past_parent = false; | ||
let only_warn = false; | ||
// can't add form to interactive elements because those are also used by the parser | ||
// to check for the last auto-closing parent. | ||
if (node.name === 'form') { | ||
const path = context.path; | ||
for (let parent of path) { | ||
if (parent.type === 'RegularElement' && parent.name === 'form') { | ||
e.node_invalid_placement(node, `<${node.name}>`, parent.name); | ||
} | ||
} | ||
} | ||
for (let i = context.path.length - 1; i >= 0; i--) { | ||
const ancestor = context.path[i]; | ||
if (interactive_elements.has(node.name)) { | ||
const path = context.path; | ||
for (let parent of path) { | ||
if ( | ||
parent.type === 'RegularElement' && | ||
parent.name === node.name && | ||
interactive_elements.has(parent.name) | ||
ancestor.type === 'IfBlock' || | ||
ancestor.type === 'EachBlock' || | ||
ancestor.type === 'AwaitBlock' || | ||
ancestor.type === 'KeyBlock' | ||
) { | ||
e.node_invalid_placement(node, `<${node.name}>`, parent.name); | ||
// We're creating a separate template string inside blocks, which means client-side this would work | ||
only_warn = true; | ||
} | ||
} | ||
} | ||
if (disallowed_paragraph_contents.includes(node.name)) { | ||
const path = context.path; | ||
for (let parent of path) { | ||
if (parent.type === 'RegularElement' && parent.name === 'p') { | ||
e.node_invalid_placement(node, `<${node.name}>`, parent.name); | ||
if (!past_parent) { | ||
if ( | ||
ancestor.type === 'RegularElement' && | ||
ancestor.name === context.state.parent_element | ||
) { | ||
if (!is_tag_valid_with_parent(node.name, context.state.parent_element)) { | ||
if (only_warn) { | ||
w.node_invalid_placement_ssr( | ||
node, | ||
`\`<${node.name}>\``, | ||
context.state.parent_element | ||
); | ||
} else { | ||
e.node_invalid_placement(node, `\`<${node.name}>\``, context.state.parent_element); | ||
} | ||
} | ||
past_parent = true; | ||
} | ||
} else if (ancestor.type === 'RegularElement') { | ||
if (!is_tag_valid_with_ancestor(node.name, ancestor.name)) { | ||
if (only_warn) { | ||
w.node_invalid_placement_ssr(node, `\`<${node.name}>\``, ancestor.name); | ||
} else { | ||
e.node_invalid_placement(node, `\`<${node.name}>\``, ancestor.name); | ||
} | ||
} | ||
} else if ( | ||
ancestor.type === 'Component' || | ||
ancestor.type === 'SvelteComponent' || | ||
ancestor.type === 'SvelteElement' || | ||
ancestor.type === 'SvelteSelf' || | ||
ancestor.type === 'SnippetBlock' | ||
) { | ||
break; | ||
} | ||
@@ -826,3 +840,3 @@ } | ||
if (!is_tag_valid_with_parent('#text', context.state.parent_element)) { | ||
e.node_invalid_placement(node, '{expression}', context.state.parent_element); | ||
e.node_invalid_placement(node, '`{expression}`', context.state.parent_element); | ||
} | ||
@@ -829,0 +843,0 @@ } |
@@ -82,2 +82,7 @@ /** @import { ArrowFunctionExpression, AssignmentExpression, BinaryOperator, Expression, FunctionDeclaration, FunctionExpression, Identifier, MemberExpression, Node, Pattern, PrivateIdentifier, Statement } from 'estree' */ | ||
if (Object.hasOwn(state.getters, node.name)) { | ||
const getter = state.getters[node.name]; | ||
return typeof getter === 'function' ? getter(node) : getter; | ||
} | ||
if (binding.node.name === '$$props') { | ||
@@ -92,7 +97,2 @@ // Special case for $$props which only exists in the old world | ||
if (Object.hasOwn(state.getters, node.name)) { | ||
const getter = state.getters[node.name]; | ||
return typeof getter === 'function' ? getter(node) : getter; | ||
} | ||
if (binding.kind === 'prop' || binding.kind === 'bindable_prop') { | ||
@@ -99,0 +99,0 @@ if (is_prop_source(binding, state)) { |
@@ -595,2 +595,3 @@ /** @import { ClassDeclaration, Expression, FunctionDeclaration, Identifier, ImportDeclaration, MemberExpression, Node, Pattern, VariableDeclarator } from 'estree' */ | ||
node.metadata = { | ||
keyed: false, | ||
contains_group_binding: false, | ||
@@ -597,0 +598,0 @@ array_name: needs_array_deduplication ? state.scope.root.unique('$$array') : null, |
@@ -396,2 +396,3 @@ import type { Binding, Css } from '#compiler'; | ||
metadata: { | ||
keyed: boolean; | ||
contains_group_binding: boolean; | ||
@@ -398,0 +399,0 @@ /** Set if something in the array expression is shadowed within the each block */ |
@@ -117,2 +117,3 @@ /* This file is generated by scripts/process-messages/index.js. Do not edit! */ | ||
"event_directive_deprecated", | ||
"node_invalid_placement_ssr", | ||
"slot_element_deprecated", | ||
@@ -744,2 +745,12 @@ "svelte_element_invalid_this" | ||
/** | ||
* %thing% is invalid inside `<%parent%>`. When rendering this component on the server, the resulting HTML will be modified by the browser, likely resulting in a `hydration_mismatch` warning | ||
* @param {null | NodeLike} node | ||
* @param {string} thing | ||
* @param {string} parent | ||
*/ | ||
export function node_invalid_placement_ssr(node, thing, parent) { | ||
w(node, "node_invalid_placement_ssr", `${thing} is invalid inside \`<${parent}>\`. When rendering this component on the server, the resulting HTML will be modified by the browser, likely resulting in a \`hydration_mismatch\` warning`); | ||
} | ||
/** | ||
* Using `<slot>` to render parent content is deprecated. Use `{@render ...}` tags instead | ||
@@ -746,0 +757,0 @@ * @param {null | NodeLike} node |
@@ -119,172 +119,3 @@ export const EACH_ITEM_REACTIVE = 1; | ||
// while `input` is also an interactive element, it is never moved by the browser, so we don't need to check for it | ||
export const interactive_elements = new Set([ | ||
'a', | ||
'button', | ||
'iframe', | ||
'embed', | ||
'select', | ||
'textarea' | ||
]); | ||
export const disallowed_paragraph_contents = [ | ||
'address', | ||
'article', | ||
'aside', | ||
'blockquote', | ||
'details', | ||
'div', | ||
'dl', | ||
'fieldset', | ||
'figcapture', | ||
'figure', | ||
'footer', | ||
'form', | ||
'h1', | ||
'h2', | ||
'h3', | ||
'h4', | ||
'h5', | ||
'h6', | ||
'header', | ||
'hr', | ||
'menu', | ||
'nav', | ||
'ol', | ||
'pre', | ||
'section', | ||
'table', | ||
'ul', | ||
'p' | ||
]; | ||
// https://html.spec.whatwg.org/multipage/syntax.html#generate-implied-end-tags | ||
const implied_end_tags = ['dd', 'dt', 'li', 'option', 'optgroup', 'p', 'rp', 'rt']; | ||
/** | ||
* @param {string} tag | ||
* @param {string} parent_tag | ||
* @returns {boolean} | ||
*/ | ||
export function is_tag_valid_with_parent(tag, parent_tag) { | ||
// First, let's check if we're in an unusual parsing mode... | ||
switch (parent_tag) { | ||
// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inselect | ||
case 'select': | ||
return ( | ||
tag === 'option' || | ||
tag === 'optgroup' || | ||
tag === '#text' || | ||
tag === 'hr' || | ||
tag === 'script' || | ||
tag === 'template' | ||
); | ||
case 'optgroup': | ||
return tag === 'option' || tag === '#text'; | ||
// Strictly speaking, seeing an <option> doesn't mean we're in a <select> | ||
// but | ||
case 'option': | ||
return tag === '#text'; | ||
// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intd | ||
// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-incaption | ||
// No special behavior since these rules fall back to "in body" mode for | ||
// all except special table nodes which cause bad parsing behavior anyway. | ||
// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intr | ||
case 'tr': | ||
return ( | ||
tag === 'th' || tag === 'td' || tag === 'style' || tag === 'script' || tag === 'template' | ||
); | ||
// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intbody | ||
case 'tbody': | ||
case 'thead': | ||
case 'tfoot': | ||
return tag === 'tr' || tag === 'style' || tag === 'script' || tag === 'template'; | ||
// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-incolgroup | ||
case 'colgroup': | ||
return tag === 'col' || tag === 'template'; | ||
// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intable | ||
case 'table': | ||
return ( | ||
tag === 'caption' || | ||
tag === 'colgroup' || | ||
tag === 'tbody' || | ||
tag === 'tfoot' || | ||
tag === 'thead' || | ||
tag === 'style' || | ||
tag === 'script' || | ||
tag === 'template' | ||
); | ||
// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inhead | ||
case 'head': | ||
return ( | ||
tag === 'base' || | ||
tag === 'basefont' || | ||
tag === 'bgsound' || | ||
tag === 'link' || | ||
tag === 'meta' || | ||
tag === 'title' || | ||
tag === 'noscript' || | ||
tag === 'noframes' || | ||
tag === 'style' || | ||
tag === 'script' || | ||
tag === 'template' | ||
); | ||
// https://html.spec.whatwg.org/multipage/semantics.html#the-html-element | ||
case 'html': | ||
return tag === 'head' || tag === 'body' || tag === 'frameset'; | ||
case 'frameset': | ||
return tag === 'frame'; | ||
case '#document': | ||
return tag === 'html'; | ||
} | ||
// Probably in the "in body" parsing mode, so we outlaw only tag combos | ||
// where the parsing rules cause implicit opens or closes to be added. | ||
// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inbody | ||
switch (tag) { | ||
case 'h1': | ||
case 'h2': | ||
case 'h3': | ||
case 'h4': | ||
case 'h5': | ||
case 'h6': | ||
return ( | ||
parent_tag !== 'h1' && | ||
parent_tag !== 'h2' && | ||
parent_tag !== 'h3' && | ||
parent_tag !== 'h4' && | ||
parent_tag !== 'h5' && | ||
parent_tag !== 'h6' | ||
); | ||
case 'rp': | ||
case 'rt': | ||
return implied_end_tags.indexOf(parent_tag) === -1; | ||
case 'body': | ||
case 'caption': | ||
case 'col': | ||
case 'colgroup': | ||
case 'frameset': | ||
case 'frame': | ||
case 'head': | ||
case 'html': | ||
case 'tbody': | ||
case 'td': | ||
case 'tfoot': | ||
case 'th': | ||
case 'thead': | ||
case 'tr': | ||
// These tags are only valid with a few parents that have special child | ||
// parsing rules -- if we're down here, then none of those matched and | ||
// so we allow it only if we don't know what the parent is, as all other | ||
// cases are invalid. | ||
return parent_tag == null; | ||
} | ||
return true; | ||
} | ||
/** | ||
* @param {string} name | ||
@@ -291,0 +122,0 @@ * @param {"include-on" | "exclude-on"} [mode] - wether if name starts with `on` or `on` is excluded at this point |
@@ -76,9 +76,4 @@ import { DEV } from 'esm-env'; | ||
for (var index of group_index) { | ||
var group = binding_group; | ||
// @ts-ignore | ||
binding_group = group[index]; | ||
if (binding_group === undefined) { | ||
// @ts-ignore | ||
binding_group = group[index] = []; | ||
} | ||
// @ts-expect-error | ||
binding_group = binding_group[index] ??= []; | ||
} | ||
@@ -85,0 +80,0 @@ } |
/** @import { Component, Payload } from '#server' */ | ||
import { FILENAME } from '../../constants.js'; | ||
import { | ||
FILENAME, | ||
disallowed_paragraph_contents, | ||
interactive_elements, | ||
is_tag_valid_with_ancestor, | ||
is_tag_valid_with_parent | ||
} from '../../constants.js'; | ||
} from '../../html-tree-validation.js'; | ||
import { current_component } from './context.js'; | ||
@@ -43,3 +42,3 @@ | ||
var message = | ||
`${stringify(parent)} cannot contain ${stringify(child)}\n\n` + | ||
`node_invalid_placement_ssr: ${stringify(parent)} cannot contain ${stringify(child)}\n\n` + | ||
'This can cause content to shift around as the browser repairs the HTML, and will likely result in a `hydration_mismatch` warning.'; | ||
@@ -64,2 +63,3 @@ | ||
var child = { tag, parent, filename, line, column }; | ||
var ancestor = parent?.parent; | ||
@@ -70,24 +70,9 @@ if (parent !== null && !is_tag_valid_with_parent(tag, parent.tag)) { | ||
if (interactive_elements.has(tag)) { | ||
let element = parent; | ||
while (element !== null) { | ||
if (interactive_elements.has(element.tag)) { | ||
print_error(payload, element, child); | ||
break; | ||
} | ||
element = element.parent; | ||
while (ancestor != null) { | ||
if (!is_tag_valid_with_ancestor(tag, ancestor.tag)) { | ||
print_error(payload, ancestor, child); | ||
} | ||
ancestor = ancestor.parent; | ||
} | ||
if (disallowed_paragraph_contents.includes(tag)) { | ||
let element = parent; | ||
while (element !== null) { | ||
if (element.tag === 'p') { | ||
print_error(payload, element, child); | ||
break; | ||
} | ||
element = element.parent; | ||
} | ||
} | ||
parent = child; | ||
@@ -94,0 +79,0 @@ } |
@@ -9,3 +9,3 @@ // generated during release, do not modify | ||
*/ | ||
export const VERSION = '5.0.0-next.200'; | ||
export const VERSION = '5.0.0-next.201'; | ||
export const PUBLIC_VERSION = '5'; |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
2169323
250
47956