diff --git a/CHANGELOG.md b/CHANGELOG.md index 18387d2090..fffeb964be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Svelte changelog +## Unreleased + +* Throw a parser error for `class:` directives with an empty class name ([#5858](https://github.com/sveltejs/svelte/issues/5858)) +* Fix type inference for derived stores ([#5935](https://github.com/sveltejs/svelte/pull/5935)) +* Make parameters of built-in animations and transitions optional ([#5936](https://github.com/sveltejs/svelte/pull/5936)) +* Make `SvelteComponentDev` typings more forgiving ([#5937](https://github.com/sveltejs/svelte/pull/5937)) + ## 3.32.0 * Allow multiple instances of the same action on an element ([#5516](https://github.com/sveltejs/svelte/issues/5516)) diff --git a/site/content/docs/02-template-syntax.md b/site/content/docs/02-template-syntax.md index 7288b8e8dd..2c665376dc 100644 --- a/site/content/docs/02-template-syntax.md +++ b/site/content/docs/02-template-syntax.md @@ -1292,19 +1292,19 @@ Components can have child content, in the same way that elements can. The content is exposed in the child component using the `` element, which can contain fallback content that is rendered if no children are provided. ```sv - - - - -

this is some child content that will overwrite the default slot content

-
-
this fallback content will be rendered when no content is provided, like in the first example
+ + + + + +

this is some child content that will overwrite the default slot content

+
``` #### [``](slot_name) @@ -1314,18 +1314,18 @@ The content is exposed in the child component using the `` element, which Named slots allow consumers to target specific areas. They can also have fallback content. ```sv - - -

Hello

-

Copyright (c) 2019 Svelte Industries

-
-
No header was provided

Some content between header and footer

+ + + +

Hello

+

Copyright (c) 2019 Svelte Industries

+
``` #### [`$$slots`](slots_object) @@ -1337,20 +1337,21 @@ Named slots allow consumers to target specific areas. They can also have fallbac Note that explicitly passing in an empty named slot will add that slot's name to `$$slots`. For example, if a parent passes `
` to a child component, `$$slots.title` will be truthy within the child. ```sv - - -

Blog Post Title

-
-
{#if $$slots.description} - +
{/if}
+ + + +

Blog Post Title

+ +
``` #### [``](slot_let) @@ -1362,11 +1363,6 @@ Slots can be rendered zero or more times, and can pass values *back* to the pare The usual shorthand rules apply — `let:item` is equivalent to `let:item={item}`, and `` is equivalent to ``. ```sv - - -
{thing.text}
-
-
    {#each items as item} @@ -1375,6 +1371,11 @@ The usual shorthand rules apply — `let:item` is equivalent to `let:item={item} {/each}
+ + + +
{thing.text}
+
``` --- @@ -1382,12 +1383,6 @@ The usual shorthand rules apply — `let:item` is equivalent to `let:item={item} Named slots can also expose values. The `let:` directive goes on the element with the `slot` attribute. ```sv - - -
{item.text}
-

Copyright (c) 2019 Svelte Industries

-
-
    {#each items as item} @@ -1398,6 +1393,12 @@ Named slots can also expose values. The `let:` directive goes on the element wit
+ + + +
{item.text}
+

Copyright (c) 2019 Svelte Industries

+
``` diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index 3b1f873ba5..8e7deb7a78 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -485,7 +485,7 @@ export default function dom( ${css.code && b`this.shadowRoot.innerHTML = \`\`;`} - @init(this, { target: this.shadowRoot, props: ${init_props} }, ${definition}, ${has_create_fragment ? 'create_fragment': 'null'}, ${not_equal}, ${prop_indexes}, ${dirty}); + @init(this, { target: this.shadowRoot, props: ${init_props} }, ${definition}, ${has_create_fragment ? 'create_fragment' : 'null'}, ${not_equal}, ${prop_indexes}, ${dirty}); ${dev_props_check} @@ -537,7 +537,7 @@ export default function dom( constructor(options) { super(${options.dev && 'options'}); ${should_add_css && b`if (!@_document.getElementById("${component.stylesheet.id}-style")) ${add_css}();`} - @init(this, options, ${definition}, ${has_create_fragment ? 'create_fragment': 'null'}, ${not_equal}, ${prop_indexes}, ${dirty}); + @init(this, options, ${definition}, ${has_create_fragment ? 'create_fragment' : 'null'}, ${not_equal}, ${prop_indexes}, ${dirty}); ${options.dev && b`@dispatch_dev("SvelteRegisterComponent", { component: this, tagName: "${name.name}", options, id: create_fragment.name });`} ${dev_props_check} diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 269edcfca8..73c75502e3 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -321,7 +321,7 @@ export default class ElementWrapper extends Wrapper { literal.quasis.push(state.quasi); block.chunks.create.push( - b`${node}.${this.can_use_innerhtml ? 'innerHTML': 'textContent'} = ${literal};` + b`${node}.${this.can_use_innerhtml ? 'innerHTML' : 'textContent'} = ${literal};` ); } } else { diff --git a/src/compiler/compile/render_dom/wrappers/Slot.ts b/src/compiler/compile/render_dom/wrappers/Slot.ts index 699363a809..d343cfb201 100644 --- a/src/compiler/compile/render_dom/wrappers/Slot.ts +++ b/src/compiler/compile/render_dom/wrappers/Slot.ts @@ -173,7 +173,7 @@ export default class SlotWrapper extends Wrapper { if (${slot}.p && ${renderer.dirty(dynamic_dependencies)}) { @update_slot_spread(${slot}, ${slot_definition}, #ctx, ${renderer.reference('$$scope')}, #dirty, ${get_slot_changes_fn}, ${get_slot_spread_changes_fn}, ${get_slot_context_fn}); } - `: b` + ` : b` if (${slot}.p && ${renderer.dirty(dynamic_dependencies)}) { @update_slot(${slot}, ${slot_definition}, #ctx, ${renderer.reference('$$scope')}, #dirty, ${get_slot_changes_fn}, ${get_slot_context_fn}); } diff --git a/src/compiler/parse/state/mustache.ts b/src/compiler/parse/state/mustache.ts index 33b589948d..ccb2d1d487 100644 --- a/src/compiler/parse/state/mustache.ts +++ b/src/compiler/parse/state/mustache.ts @@ -196,7 +196,7 @@ export default function mustache(parser: Parser) { if (!parser.eat('}')) { parser.require_whitespace(); - await_block[is_then ? 'value': 'error'] = read_context(parser); + await_block[is_then ? 'value' : 'error'] = read_context(parser); parser.allow_whitespace(); parser.eat('}', true); } @@ -204,7 +204,7 @@ export default function mustache(parser: Parser) { const new_block: TemplateNode = { start, end: null, - type: is_then ? 'ThenBlock': 'CatchBlock', + type: is_then ? 'ThenBlock' : 'CatchBlock', children: [], skip: false }; diff --git a/src/compiler/parse/state/tag.ts b/src/compiler/parse/state/tag.ts index 696a47b649..aedffffe21 100644 --- a/src/compiler/parse/state/tag.ts +++ b/src/compiler/parse/state/tag.ts @@ -387,6 +387,13 @@ function read_attribute(parser: Parser, unique_names: Set) { }, start); } + if (type === 'Class' && directive_name === '') { + parser.error({ + code: 'invalid-class-directive', + message: 'Class binding name cannot be empty' + }, start + colon_index + 1); + } + if (value[0]) { if ((value as any[]).length > 1 || value[0].type === 'Text') { parser.error({ diff --git a/src/runtime/animate/index.ts b/src/runtime/animate/index.ts index 82ec0d5a5f..ecfd9b2923 100644 --- a/src/runtime/animate/index.ts +++ b/src/runtime/animate/index.ts @@ -16,7 +16,7 @@ interface FlipParams { easing?: (t: number) => number; } -export function flip(node: Element, animation: { from: DOMRect; to: DOMRect }, params: FlipParams): AnimationConfig { +export function flip(node: Element, animation: { from: DOMRect; to: DOMRect }, params: FlipParams = {}): AnimationConfig { const style = getComputedStyle(node); const transform = style.transform === 'none' ? '' : style.transform; const scaleX = animation.from.width / node.clientWidth; diff --git a/src/runtime/internal/dev.ts b/src/runtime/internal/dev.ts index 53f6ef3bf1..99ff067474 100644 --- a/src/runtime/internal/dev.ts +++ b/src/runtime/internal/dev.ts @@ -115,6 +115,20 @@ export class SvelteComponentDev extends SvelteComponent { * ### DO NOT USE! */ $$prop_def: Props; + /** + * @private + * For type checking capabilities only. + * Does not exist at runtime. + * ### DO NOT USE! + */ + $$events_def: any; + /** + * @private + * For type checking capabilities only. + * Does not exist at runtime. + * ### DO NOT USE! + */ + $$slot_def: any; constructor(options: { target: Element; diff --git a/src/runtime/motion/spring.ts b/src/runtime/motion/spring.ts index 8d2056256c..ff625ec41f 100644 --- a/src/runtime/motion/spring.ts +++ b/src/runtime/motion/spring.ts @@ -14,7 +14,7 @@ function tick_spring(ctx: TickContext, last_value: T, current_value: T, ta // @ts-ignore const delta = target_value - current_value; // @ts-ignore - const velocity = (current_value - last_value) / (ctx.dt||1/60); // guard div by 0 + const velocity = (current_value - last_value) / (ctx.dt || 1 / 60); // guard div by 0 const spring = ctx.opts.stiffness * delta; const damper = ctx.opts.damping * velocity; const acceleration = (spring - damper) * ctx.inv_mass; @@ -80,7 +80,7 @@ export function spring(value?: T, opts: SpringOpts = {}): Spring { let inv_mass_recovery_rate = 0; let cancel_task = false; - function set(new_value: T, opts: SpringUpdateOpts={}): Promise { + function set(new_value: T, opts: SpringUpdateOpts = {}): Promise { target_value = new_value; const token = current_token = {}; diff --git a/src/runtime/store/index.ts b/src/runtime/store/index.ts index 09040487f0..c8e2b4ff99 100644 --- a/src/runtime/store/index.ts +++ b/src/runtime/store/index.ts @@ -125,10 +125,12 @@ type StoresValues = T extends Readable ? U : * * @param stores - input stores * @param fn - function callback that aggregates the values + * @param initial_value - when used asynchronously */ export function derived( stores: S, - fn: (values: StoresValues) => T + fn: (values: StoresValues, set: (value: T) => void) => Unsubscriber | void, + initial_value?: T ): Readable; /** @@ -137,12 +139,10 @@ export function derived( * * @param stores - input stores * @param fn - function callback that aggregates the values - * @param initial_value - when used asynchronously */ export function derived( stores: S, - fn: (values: StoresValues, set: (value: T) => void) => Unsubscriber | void, - initial_value?: T + fn: (values: StoresValues) => T ): Readable; export function derived(stores: Stores, fn: Function, initial_value?: T): Readable { diff --git a/src/runtime/transition/index.ts b/src/runtime/transition/index.ts index 7e879cb941..650688b6e0 100644 --- a/src/runtime/transition/index.ts +++ b/src/runtime/transition/index.ts @@ -25,7 +25,7 @@ export function blur(node: Element, { easing = cubicInOut, amount = 5, opacity = 0 -}: BlurParams): TransitionConfig { +}: BlurParams = {}): TransitionConfig { const style = getComputedStyle(node); const target_opacity = +style.opacity; const f = style.filter === 'none' ? '' : style.filter; @@ -50,7 +50,7 @@ export function fade(node: Element, { delay = 0, duration = 400, easing = linear -}: FadeParams): TransitionConfig { +}: FadeParams = {}): TransitionConfig { const o = +getComputedStyle(node).opacity; return { @@ -77,7 +77,7 @@ export function fly(node: Element, { x = 0, y = 0, opacity = 0 -}: FlyParams): TransitionConfig { +}: FlyParams = {}): TransitionConfig { const style = getComputedStyle(node); const target_opacity = +style.opacity; const transform = style.transform === 'none' ? '' : style.transform; @@ -104,7 +104,7 @@ export function slide(node: Element, { delay = 0, duration = 400, easing = cubicOut -}: SlideParams): TransitionConfig { +}: SlideParams = {}): TransitionConfig { const style = getComputedStyle(node); const opacity = +style.opacity; const height = parseFloat(style.height); @@ -146,7 +146,7 @@ export function scale(node: Element, { easing = cubicOut, start = 0, opacity = 0 -}: ScaleParams): TransitionConfig { +}: ScaleParams = {}): TransitionConfig { const style = getComputedStyle(node); const target_opacity = +style.opacity; const transform = style.transform === 'none' ? '' : style.transform; @@ -177,7 +177,7 @@ export function draw(node: SVGElement & { getTotalLength(): number }, { speed, duration, easing = cubicInOut -}: DrawParams): TransitionConfig { +}: DrawParams = {}): TransitionConfig { const len = node.getTotalLength(); if (duration === undefined) { @@ -237,7 +237,7 @@ export function crossfade({ fallback, ...defaults }: CrossfadeParams & { css: (t, u) => ` opacity: ${t * opacity}; transform-origin: top left; - transform: ${transform} translate(${u * dx}px,${u * dy}px) scale(${t + (1-t) * dw}, ${t + (1-t) * dh}); + transform: ${transform} translate(${u * dx}px,${u * dy}px) scale(${t + (1 - t) * dw}, ${t + (1 - t) * dh}); ` }; } diff --git a/test/parser/samples/error-empty-classname-binding/error.json b/test/parser/samples/error-empty-classname-binding/error.json new file mode 100644 index 0000000000..edc09ff8e1 --- /dev/null +++ b/test/parser/samples/error-empty-classname-binding/error.json @@ -0,0 +1,10 @@ +{ + "code": "invalid-class-directive", + "message": "Class binding name cannot be empty", + "start": { + "line": 1, + "column": 10, + "character": 10 + }, + "pos": 10 +} diff --git a/test/parser/samples/error-empty-classname-binding/input.svelte b/test/parser/samples/error-empty-classname-binding/input.svelte new file mode 100644 index 0000000000..3a4e5980ee --- /dev/null +++ b/test/parser/samples/error-empty-classname-binding/input.svelte @@ -0,0 +1 @@ +

Hello

diff --git a/test/server-side-rendering/index.ts b/test/server-side-rendering/index.ts index e465562112..f44bf17ab9 100644 --- a/test/server-side-rendering/index.ts +++ b/test/server-side-rendering/index.ts @@ -33,6 +33,10 @@ describe('ssr', () => { return setupHtmlEqual(); }); + let saved_window; + before(() => saved_window = global.window); + after(() => global.window = saved_window); + fs.readdirSync(`${__dirname}/samples`).forEach(dir => { if (dir[0] === '.') return; diff --git a/test/sourcemaps/samples/attached-sourcemap/_config.js b/test/sourcemaps/samples/attached-sourcemap/_config.js index 4e04fb0446..6d9786f6b0 100644 --- a/test/sourcemaps/samples/attached-sourcemap/_config.js +++ b/test/sourcemaps/samples/attached-sourcemap/_config.js @@ -2,16 +2,6 @@ import MagicString from 'magic-string'; let indent_size = 4; let comment_multi = true; - -// TODO -// Using magic-string's own .toUrl() method results in mysterious runtime failures. -// If tests are being run in `PUBLISH=true` mode AND at least one runtime test has been run prior to this test, then magic-string's btoa implementation fails with window not being declared. This is despite it previously checking that window.btoa is available. Presumably there's some sort of context thing going on, either with JSDOM or with Node itself. -// The tests pass when they're not run with `PUBLISH=true` (meaning, they currently pass in CI), and they also pass if you skip all runtime tests. -// I've spent too much time on this already, so for now to unblock the release, I am using the following workaround, which manually serializes the sourcemaps using Node Buffer APIs. -function toUrl(map) { - return 'data:application/json;charset=utf-8;base64,' + Buffer.from(map.toString(), 'utf-8').toString('base64'); -} - function get_processor(tag_name, search, replace) { return { [tag_name]: ({ content, filename }) => { @@ -29,8 +19,8 @@ function get_processor(tag_name, search, replace) { const map_opts = { source: filename, hires: true, includeContent: false }; const map = ms.generateMap(map_opts); const attach_line = (tag_name == 'style' || comment_multi) - ? `\n/*# sourceMappingURL=${toUrl(map)} */` - : `\n//# sourceMappingURL=${toUrl(map)}` // only in script + ? `\n/*# sourceMappingURL=${map.toUrl()} */` + : `\n//# sourceMappingURL=${map.toUrl()}` // only in script ; code = ms.toString() + attach_line; diff --git a/test/sourcemaps/samples/source-map-generator/_config.js b/test/sourcemaps/samples/source-map-generator/_config.js index fefb776f33..3cd15d9dda 100644 --- a/test/sourcemaps/samples/source-map-generator/_config.js +++ b/test/sourcemaps/samples/source-map-generator/_config.js @@ -6,7 +6,7 @@ export default { style: async ({ content, filename }) => { const src = new MagicString(content); const idx = content.indexOf('baritone'); - src.overwrite(idx, idx+'baritone'.length, 'bar'); + src.overwrite(idx, idx + 'baritone'.length, 'bar'); const map = SourceMapGenerator.fromSourceMap( await new SourceMapConsumer( diff --git a/test/sourcemaps/samples/sourcemap-basename/_config.js b/test/sourcemaps/samples/sourcemap-basename/_config.js index d50dd882a6..33146af7e7 100644 --- a/test/sourcemaps/samples/sourcemap-basename/_config.js +++ b/test/sourcemaps/samples/sourcemap-basename/_config.js @@ -19,7 +19,7 @@ export default { preprocess: [ { style: ({ content, filename }) => { - const external =`/* Filename from preprocess: ${filename} */` + external_code; + const external = `/* Filename from preprocess: ${filename} */` + external_code; return magic_string_bundle([ { code: external, filename: external_relative_filename }, { code: content, filename }