chore: custom elements validation (#10720)

- add "missing customElement option" warning
- add backwards compat support for customElement={null}
pull/10721/head
Simon H 2 years ago committed by GitHub
parent 6a01f48325
commit fff3320517
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
"svelte": patch
---
chore: custom elements validation

@ -23,10 +23,14 @@ export function compile(source, options) {
const validated = validate_component_options(options, ''); const validated = validate_component_options(options, '');
let parsed = _parse(source); let parsed = _parse(source);
const combined_options = /** @type {import('#compiler').ValidatedCompileOptions} */ ({ const { customElement: customElementOptions, ...parsed_options } = parsed.options || {};
/** @type {import('#compiler').ValidatedCompileOptions} */
const combined_options = {
...validated, ...validated,
...parsed.options ...parsed_options,
}); customElementOptions
};
if (parsed.metadata.ts) { if (parsed.metadata.ts) {
parsed = { parsed = {

@ -54,6 +54,11 @@ export default function read_options(node) {
component_options.customElement = ce; component_options.customElement = ce;
break; break;
} else if (value[0].expression.type !== 'ObjectExpression') { } else if (value[0].expression.type !== 'ObjectExpression') {
// Before Svelte 4 it was necessary to explicitly set customElement to null or else you'd get a warning.
// This is no longer necessary, but for backwards compat just skip in this case now.
if (value[0].expression.type === 'Literal' && value[0].expression.value === null) {
break;
}
error(attribute, 'invalid-svelte-option-customElement'); error(attribute, 'invalid-svelte-option-customElement');
} }

@ -374,8 +374,8 @@ export function analyze_component(root, options) {
uses_rest_props: false, uses_rest_props: false,
uses_slots: false, uses_slots: false,
uses_component_bindings: false, uses_component_bindings: false,
custom_element: options.customElement, custom_element: options.customElementOptions ?? options.customElement,
inject_styles: options.css === 'injected' || !!options.customElement, inject_styles: options.css === 'injected' || options.customElement,
accessors: options.customElement accessors: options.customElement
? true ? true
: !!options.accessors || : !!options.accessors ||
@ -399,6 +399,10 @@ export function analyze_component(root, options) {
} }
}; };
if (!options.customElement && root.options?.customElement) {
warn(analysis.warnings, root.options, [], 'missing-custom-element-compile-option');
}
if (analysis.runes) { if (analysis.runes) {
const props_refs = module.scope.references.get('$$props'); const props_refs = module.scope.references.get('$$props');
if (props_refs) { if (props_refs) {

@ -11,7 +11,7 @@ import type { SourceMap } from 'magic-string';
import type { Context } from 'zimmerframe'; import type { Context } from 'zimmerframe';
import type { Scope } from '../phases/scope.js'; import type { Scope } from '../phases/scope.js';
import * as Css from './css.js'; import * as Css from './css.js';
import type { EachBlock, Namespace, SvelteNode } from './template.js'; import type { EachBlock, Namespace, SvelteNode, SvelteOptions } from './template.js';
/** The return value of `compile` from `svelte/compiler` */ /** The return value of `compile` from `svelte/compiler` */
export interface CompileResult { export interface CompileResult {
@ -224,6 +224,7 @@ export type ValidatedCompileOptions = ValidatedModuleCompileOptions &
sourcemap: CompileOptions['sourcemap']; sourcemap: CompileOptions['sourcemap'];
legacy: Required<Required<CompileOptions>['legacy']>; legacy: Required<Required<CompileOptions>['legacy']>;
runes: CompileOptions['runes']; runes: CompileOptions['runes'];
customElementOptions: SvelteOptions['customElement'];
}; };
export type DeclarationKind = export type DeclarationKind =

@ -65,7 +65,7 @@ export interface Root extends BaseNode {
} }
export interface SvelteOptions { export interface SvelteOptions {
// start/end info (needed for Prettier, when someone wants to keep the options where they are) // start/end info (needed for warnings and for our Prettier plugin)
start: number; start: number;
end: number; end: number;
// options // options

@ -237,6 +237,11 @@ const block = {
'empty-block': () => 'Empty block' 'empty-block': () => 'Empty block'
}; };
const options = {
'missing-custom-element-compile-option': () =>
"The 'customElement' option is used when generating a custom element. Did you forget the 'customElement: true' compile option?"
};
/** @satisfies {Warnings} */ /** @satisfies {Warnings} */
const warnings = { const warnings = {
...css, ...css,
@ -247,7 +252,8 @@ const warnings = {
...state, ...state,
...components, ...components,
...legacy, ...legacy,
...block ...block,
...options
}; };
/** @typedef {typeof warnings} AllWarnings */ /** @typedef {typeof warnings} AllWarnings */

@ -1,11 +1,10 @@
import { test } from '../../assert'; import { test } from '../../assert';
import { mount } from 'svelte';
const tick = () => Promise.resolve(); const tick = () => Promise.resolve();
export default test({ export default test({
skip: true, // TODO: decide if we want to keep the customElement={null} behavior (warning about not having set the tag when in ce mode, and disabling that this way) async test({ assert, target, componentCtor: Component }) {
const component = mount(Component, { target, props: { name: 'slot' } });
async test({ assert, target, component: Component }) {
const component = new Component({ target, props: { name: 'slot' } });
await tick(); await tick();
await tick(); await tick();

@ -1,3 +1,4 @@
<!-- before Svelte 4 it was necessary to explicitly set customElement to null or else you'd get a warning. Keep this around for backwards compat -->
<svelte:options customElement={null} /> <svelte:options customElement={null} />
<script> <script>

@ -1,3 +0,0 @@
import { test } from '../../test';
export default test({ skip: true });

@ -1,14 +0,0 @@
[
{
"code": "missing-custom-element-compile-options",
"end": {
"column": 46,
"line": 1
},
"message": "The 'customElement' option is used when generating a custom element. Did you forget the 'customElement: true' compile option?",
"start": {
"column": 16,
"line": 1
}
}
]

@ -1,3 +0,0 @@
import { test } from '../../test';
export default test({ skip: true });

@ -1,14 +1,14 @@
[ [
{ {
"code": "missing-custom-element-compile-options", "code": "missing-custom-element-compile-option",
"message": "The 'customElement' option is used when generating a custom element. Did you forget the 'customElement: true' compile option?", "message": "The 'customElement' option is used when generating a custom element. Did you forget the 'customElement: true' compile option?",
"start": { "start": {
"line": 1, "line": 1,
"column": 16 "column": 0
}, },
"end": { "end": {
"line": 1, "line": 1,
"column": 46 "column": 49
} }
} }
] ]

@ -1120,7 +1120,7 @@ declare module 'svelte/compiler' {
} }
interface SvelteOptions { interface SvelteOptions {
// start/end info (needed for Prettier, when someone wants to keep the options where they are) // start/end info (needed for warnings and for our Prettier plugin)
start: number; start: number;
end: number; end: number;
// options // options

Loading…
Cancel
Save