breaking: remove foreign namespace (#12869)

* breaking: remove foreign namespace

* regenerate
pull/12880/head
Rich Harris 1 year ago committed by GitHub
parent c8f963ab97
commit 6448e07521
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
breaking: remove foreign namespace

@ -157,18 +157,10 @@ export default function read_options(node) {
component_options.namespace = 'svg';
} else if (value === NAMESPACE_MATHML) {
component_options.namespace = 'mathml';
} else if (
value === 'html' ||
value === 'mathml' ||
value === 'svg' ||
value === 'foreign'
) {
} else if (value === 'html' || value === 'mathml' || value === 'svg') {
component_options.namespace = value;
} else {
e.svelte_options_invalid_attribute_value(
attribute,
`"html", "mathml", "svg" or "foreign"`
);
e.svelte_options_invalid_attribute_value(attribute, `"html", "mathml" or "svg"`);
}
break;

@ -126,10 +126,6 @@ export function BindDirective(node, context) {
parent?.type === 'SvelteDocument' ||
parent?.type === 'SvelteBody'
) {
if (context.state.options.namespace === 'foreign' && node.name !== 'this') {
e.bind_invalid_name(node, node.name, 'Foreign elements only support `bind:this`');
}
if (node.name in binding_properties) {
const property = binding_properties[node.name];
if (property.valid_elements && !property.valid_elements.includes(parent.name)) {

@ -24,11 +24,7 @@ export function RegularElement(node, context) {
context.state.analysis.elements.push(node);
// Special case: Move the children of <textarea> into a value attribute if they are dynamic
if (
context.state.options.namespace !== 'foreign' &&
node.name === 'textarea' &&
node.fragment.nodes.length > 0
) {
if (node.name === 'textarea' && node.fragment.nodes.length > 0) {
for (const attribute of node.attributes) {
if (attribute.type === 'Attribute' && attribute.name === 'value') {
e.textarea_invalid_content(node);
@ -65,7 +61,6 @@ export function RegularElement(node, context) {
// Special case: single expression tag child of option element -> add "fake" attribute
// to ensure that value types are the same (else for example numbers would be strings)
if (
context.state.options.namespace !== 'foreign' &&
node.name === 'option' &&
node.fragment.nodes?.length === 1 &&
node.fragment.nodes[0].type === 'ExpressionTag' &&
@ -90,10 +85,8 @@ export function RegularElement(node, context) {
(attribute) => attribute.type === 'SpreadAttribute'
);
if (context.state.options.namespace !== 'foreign') {
node.metadata.svg = is_svg(node.name);
node.metadata.mathml = is_mathml(node.name);
}
node.metadata.svg = is_svg(node.name);
node.metadata.mathml = is_mathml(node.name);
if (context.state.parent_element) {
let past_parent = false;
@ -156,7 +149,6 @@ export function RegularElement(node, context) {
if (
context.state.analysis.source[node.end - 2] === '/' &&
context.state.options.namespace !== 'foreign' &&
!is_void(node_name) &&
!is_svg(node_name)
) {

@ -687,9 +687,6 @@ function get_static_text_value(attribute) {
* @param {AnalysisState} state
*/
export function check_element(node, state) {
// foreign namespace means elements can have completely different meanings, therefore we don't check them
if (state.options.namespace === 'foreign') return;
/** @type {Map<string, Attribute>} */
const attribute_map = new Map();

@ -79,7 +79,7 @@ export function validate_element(node, context) {
validate_slot_attribute(context, attribute);
}
if (attribute.name === 'is' && context.state.options.namespace !== 'foreign') {
if (attribute.name === 'is') {
w.attribute_avoid_is(attribute);
}

@ -178,14 +178,6 @@ export function RegularElement(node, context) {
}
}
if (child_metadata.namespace === 'foreign') {
// input/select etc could mean something completely different in foreign namespace, so don't special-case them
needs_content_reset = false;
needs_input_reset = false;
needs_special_value_handling = false;
value_binding = null;
}
if (is_content_editable && has_content_editable_binding) {
child_metadata.bound_contenteditable = true;
}
@ -219,7 +211,7 @@ export function RegularElement(node, context) {
node,
node_id,
// If value binding exists, that one takes care of calling $.init_select
value_binding === null && node.name === 'select' && child_metadata.namespace !== 'foreign'
value_binding === null && node.name === 'select'
);
is_attributes_reactive = true;
} else {
@ -249,8 +241,6 @@ export function RegularElement(node, context) {
const value = is_text_attribute(attribute) ? attribute.value[0].data : true;
if (name !== 'class' || value) {
// TODO namespace=foreign probably doesn't want to do template stuff at all and instead use programmatic methods
// to create the elements it needs.
context.state.template.push(
` ${attribute.name}${
is_boolean_attribute(name) && value === true
@ -262,10 +252,9 @@ export function RegularElement(node, context) {
}
}
const is =
is_custom_element && child_metadata.namespace !== 'foreign'
? build_custom_element_attribute_update_assignment(node_id, attribute, context)
: build_element_attribute_update_assignment(node, node_id, attribute, context);
const is = is_custom_element
? build_custom_element_attribute_update_assignment(node_id, attribute, context)
: build_element_attribute_update_assignment(node, node_id, attribute, context);
if (is) is_attributes_reactive = true;
}
}
@ -301,8 +290,7 @@ export function RegularElement(node, context) {
locations: child_locations,
scope: /** @type {Scope} */ (context.state.scopes.get(node.fragment)),
preserve_whitespace:
context.state.preserve_whitespace ||
((node.name === 'pre' || node.name === 'textarea') && child_metadata.namespace !== 'foreign')
context.state.preserve_whitespace || node.name === 'pre' || node.name === 'textarea'
};
const { hoisted, trimmed } = clean_nodes(
@ -587,28 +575,6 @@ function build_element_attribute_update_assignment(element, node_id, attribute,
const is_mathml = context.state.metadata.namespace === 'mathml';
let { has_call, value } = build_attribute_value(attribute.value, context);
// The foreign namespace doesn't have any special handling, everything goes through the attr function
if (context.state.metadata.namespace === 'foreign') {
const statement = b.stmt(
b.call(
'$.set_attribute',
node_id,
b.literal(name),
value,
is_ignored(element, 'hydration_attribute_changed') && b.true
)
);
if (attribute.metadata.expression.has_state) {
const id = state.scope.generate(`${node_id.name}_${name}`);
build_update_assignment(state, id, undefined, value, statement);
return true;
} else {
state.init.push(statement);
return false;
}
}
if (name === 'autofocus') {
state.init.push(b.stmt(b.call('$.autofocus', node_id, value)));
return false;

@ -94,7 +94,7 @@ export function SvelteElement(node, context) {
const get_tag = b.thunk(/** @type {Expression} */ (context.visit(node.tag)));
if (dev && context.state.metadata.namespace !== 'foreign') {
if (dev) {
if (node.fragment.nodes.length > 0) {
context.state.init.push(b.stmt(b.call('$.validate_void_dynamic_element', get_tag)));
}

@ -116,11 +116,7 @@ export function build_attribute_value(value, context) {
* @param {{ state: { metadata: { namespace: Namespace }}}} context
*/
export function get_attribute_name(element, attribute, context) {
if (
!element.metadata.svg &&
!element.metadata.mathml &&
context.state.metadata.namespace !== 'foreign'
) {
if (!element.metadata.svg && !element.metadata.mathml) {
return normalize_attribute(attribute.name);
}

@ -21,8 +21,7 @@ export function RegularElement(node, context) {
...context.state,
namespace,
preserve_whitespace:
context.state.preserve_whitespace ||
((node.name === 'pre' || node.name === 'textarea') && namespace !== 'foreign')
context.state.preserve_whitespace || node.name === 'pre' || node.name === 'textarea'
};
context.state.template.push(b.literal(`<${node.name}`));
@ -95,7 +94,7 @@ export function RegularElement(node, context) {
);
}
if (!is_void(node.name) || namespace === 'foreign') {
if (!is_void(node.name)) {
state.template.push(b.literal(`</${node.name}>`));
}

@ -268,7 +268,7 @@ export function build_element_attributes(node, context) {
*/
function get_attribute_name(element, attribute, context) {
let name = attribute.name;
if (!element.metadata.svg && !element.metadata.mathml && context.state.namespace !== 'foreign') {
if (!element.metadata.svg && !element.metadata.mathml) {
name = name.toLowerCase();
// don't lookup boolean aliases here, the server runtime function does only
// check for the lowercase variants of boolean attributes

@ -306,32 +306,30 @@ export function clean_nodes(
* @param {Compiler.SvelteNode[]} nodes
*/
export function infer_namespace(namespace, parent, nodes) {
if (namespace !== 'foreign') {
if (parent.type === 'RegularElement' && parent.name === 'foreignObject') {
return 'html';
}
if (parent.type === 'RegularElement' && parent.name === 'foreignObject') {
return 'html';
}
if (parent.type === 'RegularElement' || parent.type === 'SvelteElement') {
if (parent.metadata.svg) {
return 'svg';
}
return parent.metadata.mathml ? 'mathml' : 'html';
if (parent.type === 'RegularElement' || parent.type === 'SvelteElement') {
if (parent.metadata.svg) {
return 'svg';
}
return parent.metadata.mathml ? 'mathml' : 'html';
}
// Re-evaluate the namespace inside slot nodes that reset the namespace
if (
parent.type === 'Fragment' ||
parent.type === 'Root' ||
parent.type === 'Component' ||
parent.type === 'SvelteComponent' ||
parent.type === 'SvelteFragment' ||
parent.type === 'SnippetBlock' ||
parent.type === 'SlotElement'
) {
const new_namespace = check_nodes_for_namespace(nodes, 'keep');
if (new_namespace !== 'keep' && new_namespace !== 'maybe_html') {
return new_namespace;
}
// Re-evaluate the namespace inside slot nodes that reset the namespace
if (
parent.type === 'Fragment' ||
parent.type === 'Root' ||
parent.type === 'Component' ||
parent.type === 'SvelteComponent' ||
parent.type === 'SvelteFragment' ||
parent.type === 'SnippetBlock' ||
parent.type === 'SlotElement'
) {
const new_namespace = check_nodes_for_namespace(nodes, 'keep');
if (new_namespace !== 'keep' && new_namespace !== 'maybe_html') {
return new_namespace;
}
}
@ -401,10 +399,6 @@ function check_nodes_for_namespace(nodes, namespace) {
* @returns {Compiler.Namespace}
*/
export function determine_namespace_for_children(node, namespace) {
if (namespace === 'foreign') {
return namespace;
}
if (node.name === 'foreignObject') {
return 'html';
}

@ -85,7 +85,7 @@ export interface CompileOptions extends ModuleCompileOptions {
*/
accessors?: boolean;
/**
* The namespace of the element; e.g., `"html"`, `"svg"`, `"foreign"`.
* The namespace of the element; e.g., `"html"`, `"svg"`, `"mathml"`.
*
* @default 'html'
*/

@ -47,11 +47,8 @@ export interface Fragment {
* - `html` the default, for e.g. `<div>` or `<span>`
* - `svg` for e.g. `<svg>` or `<g>`
* - `mathml` for e.g. `<math>` or `<mrow>`
* - `foreign` for other compilation targets than the web, e.g. Svelte Native.
* Disallows bindings other than bind:this, disables a11y checks, disables any special attribute handling
* (also see https://github.com/sveltejs/svelte/pull/5652)
*/
export type Namespace = 'html' | 'svg' | 'mathml' | 'foreign';
export type Namespace = 'html' | 'svg' | 'mathml';
export interface Root extends BaseNode {
type: 'Root';

@ -90,7 +90,7 @@ export const validate_component_options =
name: string(undefined),
namespace: list(['html', 'svg', 'foreign']),
namespace: list(['html', 'mathml', 'svg']),
modernAst: boolean(false),

@ -1,26 +0,0 @@
import { test } from '../../test';
export default test({
// TODO: needs fixing. Can only be fixed once we also support document.createElementNS-style creation of elements
// because the $.template('...') approach has no option to preserve attribute name casing
skip: true,
html: `
<page horizontalAlignment="center">
<button textWrap="true" text="button"></button>
<text wordWrap="true"></text>
</page>
`,
compileOptions: {
namespace: 'foreign'
},
test({ assert, target }) {
// @ts-ignore
const attr = (/** @type {string} */ sel) => target.querySelector(sel).attributes[0].name;
assert.equal(attr('page'), 'horizontalAlignment');
assert.equal(attr('button'), 'textWrap');
assert.equal(attr('text'), 'wordWrap');
}
});

@ -1,4 +0,0 @@
<page horizontalAlignment="center">
<button textWrap="true" text="button"></button>
<text wordWrap="true" />
</page>

@ -1,20 +0,0 @@
import { test } from '../../test';
export default test({
// TODO: needs fixing. Can only be fixed once we also support document.createElementNS-style creation of elements
// because the $.template('...') approach has no option to preserve attribute name casing
skip: true,
html: `
<page horizontalAlignment="center">
<button textWrap="true" text="button">
</page>
`,
test({ assert, target }) {
// @ts-ignore
const attr = (/** @type {string} */ sel) => target.querySelector(sel).attributes[0].name;
assert.equal(attr('page'), 'horizontalAlignment');
assert.equal(attr('button'), 'textWrap');
}
});

@ -1,4 +0,0 @@
<svelte:options namespace="foreign" />
<page horizontalAlignment="center">
<button textWrap="true" text="button">
</page>

@ -1,7 +0,0 @@
<svelte:options namespace="foreign" />
<page>
<a>not actually a link</a>
<label>This isn't a html label</label>
<figure>This is maybe a QT figure</figure>
</page>

@ -1,14 +0,0 @@
[
{
"code": "bind_invalid_name",
"message": "`bind:value` is not a valid binding. Foreign elements only support `bind:this`",
"start": {
"line": 6,
"column": 7
},
"end": {
"line": 6,
"column": 28
}
}
]

@ -1,6 +0,0 @@
<svelte:options namespace="foreign" />
<script>
let whatever;
</script>
<input bind:value={whatever} />

@ -1,7 +1,7 @@
[
{
"code": "svelte_options_invalid_attribute_value",
"message": "Value must be \"html\", \"mathml\", \"svg\" or \"foreign\", if specified",
"message": "Value must be \"html\", \"mathml\" or \"svg\", if specified",
"start": {
"line": 1,
"column": 16

@ -1,7 +1,7 @@
[
{
"code": "svelte_options_invalid_attribute_value",
"message": "Value must be \"html\", \"mathml\", \"svg\" or \"foreign\", if specified",
"message": "Value must be \"html\", \"mathml\" or \"svg\", if specified",
"start": {
"line": 1,
"column": 16

@ -128,21 +128,5 @@ it('errors if namespace is provided but unrecognised', () => {
// @ts-expect-error
namespace: 'svefefe'
});
}, /namespace should be one of "html", "svg" or "foreign"/);
});
it("does not throw error if 'this' is bound for foreign element", () => {
assert.doesNotThrow(() => {
compile(
`
<script>
let whatever;
</script>
<div bind:this={whatever}></div>`,
{
name: 'test',
namespace: 'foreign'
}
);
});
}, /namespace should be one of "html", "mathml" or "svg"/);
});

@ -765,7 +765,7 @@ declare module 'svelte/compiler' {
*/
accessors?: boolean;
/**
* The namespace of the element; e.g., `"html"`, `"svg"`, `"foreign"`.
* The namespace of the element; e.g., `"html"`, `"svg"`, `"mathml"`.
*
* @default 'html'
*/
@ -1504,11 +1504,8 @@ declare module 'svelte/compiler' {
* - `html` the default, for e.g. `<div>` or `<span>`
* - `svg` for e.g. `<svg>` or `<g>`
* - `mathml` for e.g. `<math>` or `<mrow>`
* - `foreign` for other compilation targets than the web, e.g. Svelte Native.
* Disallows bindings other than bind:this, disables a11y checks, disables any special attribute handling
* (also see https://github.com/sveltejs/svelte/pull/5652)
*/
type Namespace = 'html' | 'svg' | 'mathml' | 'foreign';
type Namespace = 'html' | 'svg' | 'mathml';
interface Root extends BaseNode {
type: 'Root';
@ -2641,7 +2638,7 @@ declare module 'svelte/types/compiler/interfaces' {
*/
accessors?: boolean;
/**
* The namespace of the element; e.g., `"html"`, `"svg"`, `"foreign"`.
* The namespace of the element; e.g., `"html"`, `"svg"`, `"mathml"`.
*
* @default 'html'
*/
@ -2776,11 +2773,8 @@ declare module 'svelte/types/compiler/interfaces' {
* - `html` the default, for e.g. `<div>` or `<span>`
* - `svg` for e.g. `<svg>` or `<g>`
* - `mathml` for e.g. `<math>` or `<mrow>`
* - `foreign` for other compilation targets than the web, e.g. Svelte Native.
* Disallows bindings other than bind:this, disables a11y checks, disables any special attribute handling
* (also see https://github.com/sveltejs/svelte/pull/5652)
*/
type Namespace = 'html' | 'svg' | 'mathml' | 'foreign';
type Namespace = 'html' | 'svg' | 'mathml';
type ICompileDiagnostic = {
code: string;
message: string;

Loading…
Cancel
Save