diff --git a/.eslintignore b/.eslintignore index bfe7b1fa95..b5cb03ae6e 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,6 +4,9 @@ _output test/*/samples/*/output.js node_modules +# automatically generated +internal_exports.ts + # output files animate/*.js esing/*.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2488902b24..14824ecdfa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,3 +23,9 @@ jobs: - uses: actions/checkout@v1 - uses: actions/setup-node@v1 - run: 'npm i && npm run lint' + Unit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: actions/setup-node@v1 + - run: 'npm i && npm run test:unit' diff --git a/.gitignore b/.gitignore index b0e6896dbe..7a14244929 100644 --- a/.gitignore +++ b/.gitignore @@ -33,5 +33,8 @@ _output /site/static/svelte-app.json /site/static/contributors.jpg /site/static/workers +/site/static/organisations /site/scripts/svelte-app +/site/scripts/community /site/src/routes/_contributors.js +/site/src/routes/_components/WhosUsingSvelte.svelte diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 81451039fa..0000000000 --- a/.prettierrc +++ /dev/null @@ -1,3 +0,0 @@ -useTabs: true -singleQuote: true -trailingComma: es5 diff --git a/CHANGELOG.md b/CHANGELOG.md index af8c4a155d..922bc08c92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,67 @@ ## Unreleased +* Fix indirect bindings involving elements with spreads ([#3680](https://github.com/sveltejs/svelte/issues/3680)) +* Warn when using `` and `Foo` is dynamic ([#4331](https://github.com/sveltejs/svelte/issues/4331)) +* Display compilation warnings in `svelte/register` in dev mode ([#4364](https://github.com/sveltejs/svelte/issues/4364)) +* Fix unneeded updating of keyed each blocks ([#4373](https://github.com/sveltejs/svelte/issues/4373)) +* Throw runtime error in dev mode for non-array-like values in `{#each}` blocks ([#4408](https://github.com/sveltejs/svelte/issues/4408)) + +## 3.18.2 + +* Fix binding to module-level variables ([#4086](https://github.com/sveltejs/svelte/issues/4086)) +* Improve parsing error messages when there is a pending unclosed tag ([#4131](https://github.com/sveltejs/svelte/issues/4131)) +* Disallow attribute/prop names from matching two-way-bound names or `{shorthand}` attribute/prop names ([#4325](https://github.com/sveltejs/svelte/issues/4325)) +* Improve performance of `flush()` by not using `.shift()` ([#4356](https://github.com/sveltejs/svelte/pull/4356)) +* Permit reserved keywords as destructuring keys in `{#each}` ([#4372](https://github.com/sveltejs/svelte/issues/4372)) +* Disallow reserved keywords in `{expressions}` ([#4372](https://github.com/sveltejs/svelte/issues/4372)) +* Fix code generation error with precedence of arrow functions ([#4384](https://github.com/sveltejs/svelte/issues/4384)) +* Fix event handlers that are dynamic via reactive declarations or stores ([#4388](https://github.com/sveltejs/svelte/issues/4388)) +* Fix invalidation in expressions like `++foo.bar` ([#4393](https://github.com/sveltejs/svelte/issues/4393)) + +## 3.18.1 + +* Fix code generation error with adjacent inline and block comments ([#4312](https://github.com/sveltejs/svelte/issues/4312)) +* Fix detection of unused CSS selectors that begin with a `:global()` but contain a scoped portion ([#4314](https://github.com/sveltejs/svelte/issues/4314)) + +## 3.18.0 + +* Fix infinite loop when instantiating another component during `onMount` ([#3218](https://github.com/sveltejs/svelte/issues/3218)) +* Make autosubscribing to a nullish store a no-op ([#2181](https://github.com/sveltejs/svelte/issues/2181)) + +## 3.17.3 + +* Fix updating a `` inside an `{#if}` or other block ([#4292](https://github.com/sveltejs/svelte/issues/4292)) +* Fix using RxJS observables in `derived` stores ([#4298](https://github.com/sveltejs/svelte/issues/4298)) +* Add dev mode check to disallow duplicate keys in a keyed `{#each}` ([#4301](https://github.com/sveltejs/svelte/issues/4301)) +* Fix hydration of `` when starting from SSR-generated code with `hydratable: true` ([#4310](https://github.com/sveltejs/svelte/issues/4310)) + +## 3.17.2 + +* Fix removing attributes during hydration ([#1733](https://github.com/sveltejs/svelte/issues/1733)) +* Disallow two-way binding to a variable declared by an `{#await}` block ([#4012](https://github.com/sveltejs/svelte/issues/4012)) +* Allow access to `let:` variables in sibling attributes on slot root ([#4173](https://github.com/sveltejs/svelte/issues/4173)) +* Fix `~=` and class selector matching against values separated by any whitespace characters ([#4242](https://github.com/sveltejs/svelte/issues/4242)) +* Fix code generation for `await`ed expressions that need parentheses ([#4267](https://github.com/sveltejs/svelte/issues/4267)) +* Preserve JavaScript comments from the original component source where possible ([#4268](https://github.com/sveltejs/svelte/issues/4268)) +* Add some more known globals ([#4276](https://github.com/sveltejs/svelte/pull/4276)) +* Correctly apply event modifiers to `<svelte:body>` events ([#4278](https://github.com/sveltejs/svelte/issues/4278)) + +## 3.17.1 + +* Only attach SSR mode markers to a component's `<head>` elements when compiling with `hydratable: true` ([#4258](https://github.com/sveltejs/svelte/issues/4258)) + +## 3.17.0 + +* Remove old `<head>` elements during hydration so they aren't duplicated ([#1607](https://github.com/sveltejs/svelte/issues/1607)) * Prevent text input cursor jumping in Safari with one-way binding ([#3449](https://github.com/sveltejs/svelte/issues/3449)) +* Expose compiler version in dev events ([#4047](https://github.com/sveltejs/svelte/issues/4047)) +* Don't run actions before their element is in the document ([#4166](https://github.com/sveltejs/svelte/issues/4166)) +* Fix reactive assignments with destructuring and stores where the destructured value should be undefined ([#4170](https://github.com/sveltejs/svelte/issues/4170)) +* Fix hydrating `{:else}` in `{#each}` ([#4202](https://github.com/sveltejs/svelte/issues/4202)) +* Do not automatically declare variables in reactive declarations when assigning to a member expression ([#4212](https://github.com/sveltejs/svelte/issues/4212)) +* Fix stringifying of attributes in SSR mode when there are spread attributes ([#4240](https://github.com/sveltejs/svelte/issues/4240)) +* Only render one `<title>` in SSR mode when multiple components provide one ([#4250](https://github.com/sveltejs/svelte/pull/4250)) ## 3.16.7 diff --git a/LICENSE b/LICENSE index b63fe48148..a8e26a756d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2016-19 [these people](https://github.com/sveltejs/svelte/graphs/contributors) +Copyright (c) 2016-20 [these people](https://github.com/sveltejs/svelte/graphs/contributors) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/README.md b/README.md index 9460efce76..e05154bd7c 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,6 @@ <img src="https://img.shields.io/npm/v/svelte.svg" alt="npm version"> </a> - <a href="https://packagephobia.now.sh/result?p=svelte"> - <img src="https://packagephobia.now.sh/badge?p=svelte" alt="install size"> - </a> - <a href="https://github.com/sveltejs/svelte/actions"> <img src="https://github.com/sveltejs/svelte/workflows/CI/badge.svg?branch=master" alt="build status"> diff --git a/package-lock.json b/package-lock.json index adb05e1730..8f0b7469f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.16.7", + "version": "3.18.2", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -597,9 +597,9 @@ "dev": true }, "code-red": { - "version": "0.0.27", - "resolved": "https://registry.npmjs.org/code-red/-/code-red-0.0.27.tgz", - "integrity": "sha512-MSILIi8kkSGag76TW+PRfBP/dN++Ei+uTeiUfqW4Es5teFNrbsAWtyAbPwxKI1vxEsBx64loAfGxS1kVCo1d2g==", + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/code-red/-/code-red-0.1.1.tgz", + "integrity": "sha512-2pEIyya4fxI9HxQcrl9dJl20+9He1nLeja50zkf7gK2OTTV9NpD3CgA74gzXnPqWv2zJpa6uXNSaE0haOCpOsw==", "dev": true, "requires": { "acorn": "^7.1.0", @@ -1682,9 +1682,9 @@ } }, "https-proxy-agent": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.2.tgz", - "integrity": "sha512-c8Ndjc9Bkpfx/vCJueCPy0jlP4ccCCSNDp8xwCZzPjKJUm+B+u9WX2x98Qx4n1PiMNTWo3D7KK5ifNV/yJyRzg==", + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", "dev": true, "requires": { "agent-base": "^4.3.0", diff --git a/package.json b/package.json index 68d87f1ee6..6000e9d1c4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.16.7", + "version": "3.18.2", "description": "Cybernetically enhanced web apps", "module": "index.mjs", "main": "index", @@ -70,7 +70,7 @@ "acorn": "^7.1.0", "agadoo": "^1.1.0", "c8": "^5.0.1", - "code-red": "0.0.27", + "code-red": "0.1.1", "codecov": "^3.5.0", "css-tree": "1.0.0-alpha22", "eslint": "^6.3.0", diff --git a/register.js b/register.js index 3278c1cbf5..25f8aa4309 100644 --- a/register.js +++ b/register.js @@ -36,7 +36,15 @@ function registerExtension(extension) { format: 'cjs' }); - const { js } = compile(fs.readFileSync(filename, 'utf-8'), options); + const { js, warnings } = compile(fs.readFileSync(filename, 'utf-8'), options); + + if (options.dev) { + warnings.forEach(warning => { + console.warn(`\nSvelte Warning in ${warning.filename}:`); + console.warn(warning.message); + console.warn(warning.frame); + }) + } return module._compile(js.code, filename); }; diff --git a/rollup.config.js b/rollup.config.js index ac171b85f1..6f3d893a33 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -60,6 +60,9 @@ export default [ ], external, plugins: [ + replace({ + __VERSION__: pkg.version + }), ts_plugin, { writeBundle(bundle) { diff --git a/site/Dockerfile b/site/Dockerfile index c2a65122d9..0261a69521 100644 --- a/site/Dockerfile +++ b/site/Dockerfile @@ -1,3 +1,6 @@ +# IMPORTANT: Don't use this Dockerfile in your own Sapper projects without also looking at the .dockerignore file. +# Without an appropriate .dockerignore, this Dockerfile will copy a large number of unneeded files into your image. + FROM mhart/alpine-node:12 # install dependencies diff --git a/site/Makefile b/site/Makefile index 4c432dc6c9..6fd08fd8ee 100644 --- a/site/Makefile +++ b/site/Makefile @@ -14,9 +14,9 @@ sapper: docker: @echo "\n~> building docker image" - @gcloud builds submit -t $(IMAGE) + @gcloud builds submit --project $(PROJECT) -t $(IMAGE) deploy: sapper docker @echo "\n~> deploying $(SERVICE) to Cloud Run servers" - @gcloud beta run deploy $(SERVICE) --allow-unauthenticated --platform managed --region us-central1 --image $(IMAGE) --memory=512Mi + @gcloud run deploy $(SERVICE) --project $(PROJECT) --allow-unauthenticated --platform managed --region us-central1 --image $(IMAGE) --memory=512Mi diff --git a/site/content/blog/2019-04-15-setting-up-your-editor.md b/site/content/blog/2019-04-15-setting-up-your-editor.md index 7a107c590b..b80223d820 100644 --- a/site/content/blog/2019-04-15-setting-up-your-editor.md +++ b/site/content/blog/2019-04-15-setting-up-your-editor.md @@ -60,7 +60,7 @@ To treat `*.svelte` files as HTML, add the following lines to your `settings.jso ## JetBrains WebStorm -To treat `*.svelte` files as HTML in WebStorm, you need to create a new file type association. Please refer to the [JetBrains website](https://www.jetbrains.com/help/webstorm/creating-and-registering-file-types.html) to see how. +The [Svelte Framework Integration](https://plugins.jetbrains.com/plugin/12375-svelte/) can be used to add support for Svelte to WebStorm, or other Jetbrains IDEs. Consult the [WebStorm plugin installation guide](https://www.jetbrains.com/help/webstorm/managing-plugins.html) on the JetBrains website for more details. ## Sublime Text 3 diff --git a/site/content/blog/2019-04-16-svelte-for-new-developers.md b/site/content/blog/2019-04-16-svelte-for-new-developers.md index fc8e993e88..796a6c11ab 100644 --- a/site/content/blog/2019-04-16-svelte-for-new-developers.md +++ b/site/content/blog/2019-04-16-svelte-for-new-developers.md @@ -18,7 +18,7 @@ You'll be using the *command line*, also known as the terminal. On Windows, you The command line is a way to interact with your computer (or another computer! but that's a topic for another time) with more power and control than the GUI (graphical user interface) that most people use day-to-day. -Once on the command line, you can navigate the filesystem using `ls` to list the contents of your current directory, and `cd` to change the current directory. For example, if you had a `Development` directory of your projects inside your home directory, you would type +Once on the command line, you can navigate the filesystem using `ls` (`dir` on Windows) to list the contents of your current directory, and `cd` to change the current directory. For example, if you had a `Development` directory of your projects inside your home directory, you would type ```bash cd Development @@ -34,7 +34,7 @@ cd svelte-projects A full introduction to the command line is out of the scope of this guide, but here are a few more useful commands: * `cd ..` — navigates to the parent of the current directory -* `cat my-file.txt` — on Mac/Linux, lists the contents of `my-file.txt` +* `cat my-file.txt` — on Mac/Linux (`type my-file.txt` on Windows), lists the contents of `my-file.txt` * `open .` (or `start .` on Windows) — opens the current directory in Finder or File Explorer diff --git a/site/content/blog/2019-04-20-write-less-code.md b/site/content/blog/2019-04-20-write-less-code.md index d6c8b77193..d56c5312e8 100644 --- a/site/content/blog/2019-04-20-write-less-code.md +++ b/site/content/blog/2019-04-20-write-less-code.md @@ -159,6 +159,6 @@ In Vue, meanwhile, we have a default export with a `data` function that returns ## Death to boilerplate -These are just some of the ways that Svelte helps you build user interfaces with a minimum of fuss. There are plenty of others — for example, [reactive declarations](https://svelte.dev/tutorial/reactive-declarations) essentially do the work of React's `useMemo`, `useCallback` and `useEffect` without the boilerplate (or indeed the garbage collection overhead of creating inline functions and arrays on each state change). +These are just some of the ways that Svelte helps you build user interfaces with a minimum of fuss. There are plenty of others — for example, [reactive declarations](tutorial/reactive-declarations) essentially do the work of React's `useMemo`, `useCallback` and `useEffect` without the boilerplate (or indeed the garbage collection overhead of creating inline functions and arrays on each state change). How? By choosing a different set of constraints. Because [Svelte is a compiler](blog/frameworks-without-the-framework), we're not bound to the peculiarities of JavaScript: we can *design* a component authoring experience, rather than having to fit it around the semantics of the language. Paradoxically, this results in *more* idiomatic code — for example using variables naturally rather than via proxies or hooks — while delivering significantly more performant apps. diff --git a/site/content/docs/00-introduction.md b/site/content/docs/00-introduction.md index 2456b85f9c..15fdffbde2 100644 --- a/site/content/docs/00-introduction.md +++ b/site/content/docs/00-introduction.md @@ -2,8 +2,8 @@ title: Before we begin --- -> Temporary note: This document is a work-in-progress. Please forgive any missing or misleading parts, and don't be shy about asking for help in the [Discord chatroom](chat). The [tutorial](tutorial) is more complete; start there. - This page contains detailed API reference documentation. It's intended to be a resource for people who already have some familiarity with Svelte. If that's not you (yet), you may prefer to visit the [interactive tutorial](tutorial) or the [examples](examples) before consulting this reference. + +Don't be shy about asking for help in the [Discord chatroom](chat). \ No newline at end of file diff --git a/site/content/docs/01-component-format.md b/site/content/docs/01-component-format.md index 3d349af51d..026a2da5b3 100644 --- a/site/content/docs/01-component-format.md +++ b/site/content/docs/01-component-format.md @@ -147,24 +147,7 @@ If a statement consists entirely of an assignment to an undeclared variable, Sve --- -A *store* is any object that allows reactive access to a value via a simple *store contract*. - -The [`svelte/store` module](docs#svelte_store) contains minimal store implementations which fulfil this contract. You can use these as the basis for your own stores, or you can implement your stores from scratch. - -A store must contain a `.subscribe` method, which must accept as its argument a subscription function. This subscription function must be immediately and synchronously called with the store's current value upon calling `.subscribe`. All of a store's active subscription functions must later be synchronously called whenever the store's value changes. The `.subscribe` method must also return an unsubscription function. Calling an unsubscription function must stop its subscription, and its corresponding subscription function must not be called again by the store. - -A store may optionally contain a `.set` method, which must accept as its argument a new value for the store, and which synchronously calls all of the store's active subscription functions. Such a store is called a *writable store*. - -```js -const unsubscribe = store.subscribe(value => { - console.log(value); -}); // logs `value` - -// later... -unsubscribe(); -``` - ---- +A *store* is an object that allows reactive access to a value via a simple *store contract*. The [`svelte/store` module](docs#svelte_store) contains minimal store implementations which fulfil this contract. Any time you have a reference to a store, you can access its value inside a component by prefixing it with the `$` character. This causes Svelte to declare the prefixed variable, and set up a store subscription that will be unsubscribed when appropriate. @@ -189,6 +172,20 @@ Local variables (that do not represent store values) must *not* have a `$` prefi </script> ``` +##### Store contract + +```js +store = { subscribe: (subscription: (value: any) => void) => () => void, set?: (value: any) => void } +``` + +You can create your own stores without relying on [`svelte/store`](docs#svelte_store), by implementing the *store contract*: + +1. A store must contain a `.subscribe` method, which must accept as its argument a subscription function. This subscription function must be immediately and synchronously called with the store's current value upon calling `.subscribe`. All of a store's active subscription functions must later be synchronously called whenever the store's value changes. +2. The `.subscribe` method must return an unsubscribe function. Calling an unsubscribe function must stop its subscription, and its corresponding subscription function must not be called again by the store. +3. A store may *optionally* contain a `.set` method, which must accept as its argument a new value for the store, and which synchronously calls all of the store's active subscription functions. Such a store is called a *writable store*. + +For interoperability with RxJS Observables, the `.subscribe` method is also allowed to return an object with an `.unsubscribe` method, rather than return the unsubscription function directly. Note however that unless `.subscribe` synchronously calls the subscription (which is not required by the Observable spec), Svelte will see the value of the store as `undefined` until it does. + ### <script context="module"> @@ -200,7 +197,7 @@ You can `export` bindings from this block, and they will become exports of the c You cannot `export default`, since the default export is the component itself. -> Variables defined in `module` scripts are not reactive — reassigning them will not trigger a rerender even though the variable itself will update. For values shared between multiple components, consider using a [store](https://svelte.dev/docs#svelte_store). +> Variables defined in `module` scripts are not reactive — reassigning them will not trigger a rerender even though the variable itself will update. For values shared between multiple components, consider using a [store](docs#svelte_store). ```html <script context="module"> @@ -256,3 +253,15 @@ To apply styles to a selector globally, use the `:global(...)` modifier. } </style> ``` + +--- + +If you want to make @keyframes that are accessible globally, you need to prepend your keyframe names with `-global-`. + +The `-global-` part will be removed when compiled, and the keyframe then be referenced using just `my-animation-name` elsewhere in your code. + +```html +<style> + @keyframes -global-my-animation-name {...} +</style> +``` diff --git a/site/content/docs/02-template-syntax.md b/site/content/docs/02-template-syntax.md index 89506753d6..0a4ca2ee1a 100644 --- a/site/content/docs/02-template-syntax.md +++ b/site/content/docs/02-template-syntax.md @@ -205,6 +205,8 @@ Iterating over lists of values can be done with an each block. </ul> ``` +You can use each blocks to iterate over any array or array-like value — that is, any object with a `length` property. + --- An each block can also specify an *index*, equivalent to the second argument in an `array.map(...)` callback: @@ -1016,7 +1018,7 @@ DOMRect { ​top: number, width: number, x: number, - y:number + y: number } ``` @@ -1202,14 +1204,16 @@ The content is exposed in the child component using the `<slot>` element, which ```html <!-- App.svelte --> +<Widget></Widget> + <Widget> - <p>this is some child content</p> + <p>this is some child content that will overwrite the default slot content</p> </Widget> <!-- Widget.svelte --> <div> <slot> - this will be rendered if someone does <Widget/> + this fallback content will be rendered when no content is provided, like in the first example </slot> </div> ``` diff --git a/site/content/docs/03-run-time.md b/site/content/docs/03-run-time.md index 5bcbe8764c..c75ec694d9 100644 --- a/site/content/docs/03-run-time.md +++ b/site/content/docs/03-run-time.md @@ -214,7 +214,11 @@ Events dispatched from child components can be listened to in their parent. Any ### `svelte/store` -The `svelte/store` module exports functions for creating [stores](docs#4_Prefix_stores_with_$_to_access_their_values). +The `svelte/store` module exports functions for creating [readable](docs#readable), [writable](docs#writable) and [derived](docs#derived) stores. + +Keep in mind that you don't *have* to use these functions to enjoy the [reactive `$store` syntax](docs#4_Prefix_stores_with_$_to_access_their_values) in your components. Any object that correctly implements `.subscribe`, unsubscribe, and (optionally) `.set` is a valid store, and will work both with the special syntax, and with Svelte's built-in [`derived` stores](docs#derived). + +This makes it possible to wrap almost any other reactive state handling library for use in Svelte. Read more about the [store contract](docs#Store_contract) to see what a correct implementation looks like. #### `writable` @@ -883,7 +887,7 @@ Existing children of `target` are left where they are. --- -The `hydrate` option instructs Svelte to upgrade existing DOM (usually from server-side rendering) rather than creating new elements. It will only work if the component was compiled with the [`hydratable: true` option](docs#svelte_compile). +The `hydrate` option instructs Svelte to upgrade existing DOM (usually from server-side rendering) rather than creating new elements. It will only work if the component was compiled with the [`hydratable: true` option](docs#svelte_compile). Hydration of `<head>` elements only works properly if the server-side rendering code was also compiled with `hydratable: true`, which adds a marker to each element in the `<head>` so that the component knows which elements it's responsible for removing during hydration. Whereas children of `target` are normally left alone, `hydrate: true` will cause any children to be removed. For that reason, the `anchor` option cannot be used alongside `hydrate: true`. diff --git a/site/content/docs/04-compile-time.md b/site/content/docs/04-compile-time.md index e9126ccf13..0cb2ac3caf 100644 --- a/site/content/docs/04-compile-time.md +++ b/site/content/docs/04-compile-time.md @@ -68,7 +68,7 @@ The following options can be passed to the compiler. None are required: | `generate` | `"dom"` | If `"dom"`, Svelte emits a JavaScript class for mounting to the DOM. If `"ssr"`, Svelte emits an object with a `render` method suitable for server-side rendering. If `false`, no JavaScript or CSS is returned; just metadata. | `dev` | `false` | If `true`, causes extra code to be added to components that will perform runtime checks and provide debugging information during development. | `immutable` | `false` | If `true`, tells the compiler that you promise not to mutate any objects. This allows it to be less conservative about checking whether values have changed. -| `hydratable` | `false` | If `true`, enables the `hydrate: true` runtime option, which allows a component to upgrade existing DOM rather than creating new DOM from scratch. +| `hydratable` | `false` | If `true` when generating DOM code, enables the `hydrate: true` runtime option, which allows a component to upgrade existing DOM rather than creating new DOM from scratch. When generating SSR code, this adds markers to `<head>` elements so that hydration knows which to replace. | `legacy` | `false` | If `true`, generates code that will work in IE9 and IE10, which don't support things like `element.dataset`. | `accessors` | `false` | If `true`, getters and setters will be created for the component's props. If `false`, they will only be created for readonly exported values (i.e. those declared with `const`, `class` and `function`). If compiling with `customElement: true` this option defaults to `true`. | `customElement` | `false` | If `true`, tells the compiler to generate a custom element constructor instead of a regular Svelte component. @@ -76,7 +76,7 @@ The following options can be passed to the compiler. None are required: | `css` | `true` | If `true`, styles will be included in the JavaScript class and injected at runtime. It's recommended that you set this to `false` and use the CSS that is statically generated, as it will result in smaller JavaScript bundles and better performance. | `loopGuardTimeout` | 0 | A `number` that tells Svelte to break the loop if it blocks the thread for more than `loopGuardTimeout` ms. This is useful to prevent infinite loops. **Only available when `dev: true`** | `preserveComments` | `false` | If `true`, your HTML comments will be preserved during server-side rendering. By default, they are stripped out. -| `preserveWhitespace` | `false` | If `true`, whitespace inside and between elements is kept as you typed it, rather than optimised by Svelte. +| `preserveWhitespace` | `false` | If `true`, whitespace inside and between elements is kept as you typed it, rather than removed or collapsed to a single space where possible. | `outputFilename` | `null` | A `string` used for your JavaScript sourcemap. | `cssOutputFilename` | `null` | A `string` used for your CSS sourcemap. | `sveltePath` | `"svelte"` | The location of the `svelte` package. Any imports from `svelte` or `svelte/[module]` will be modified accordingly. @@ -185,7 +185,7 @@ const ast = svelte.parse(source, { filename: 'App.svelte' }); result: { code: string, dependencies: Array<string> -} = svelte.preprocess( +} = await svelte.preprocess( source: string, preprocessors: Array<{ markup?: (input: { content: string, filename: string }) => Promise<{ @@ -222,7 +222,7 @@ The `markup` function receives the entire component source text, along with the ```js const svelte = require('svelte/compiler'); -const { code } = svelte.preprocess(source, { +const { code } = await svelte.preprocess(source, { markup: ({ content, filename }) => { return { code: content.replace(/foo/g, 'bar') @@ -244,7 +244,7 @@ const svelte = require('svelte/compiler'); const sass = require('node-sass'); const { dirname } = require('path'); -const { code, dependencies } = svelte.preprocess(source, { +const { code, dependencies } = await svelte.preprocess(source, { style: async ({ content, attributes, filename }) => { // only process <style lang="sass"> if (attributes.lang !== 'sass') return; @@ -277,7 +277,7 @@ Multiple preprocessors can be used together. The output of the first becomes the ```js const svelte = require('svelte/compiler'); -const { code } = svelte.preprocess(source, [ +const { code } = await svelte.preprocess(source, [ { markup: () => { console.log('this runs first'); diff --git a/site/package.json b/site/package.json index d841808a2a..a94d264217 100644 --- a/site/package.json +++ b/site/package.json @@ -7,7 +7,7 @@ "copy-workers": "rm -rf static/workers && cp -r node_modules/@sveltejs/svelte-repl/workers static", "migrate": "node-pg-migrate -r dotenv/config", "sapper": "npm run copy-workers && sapper build --legacy", - "update": "node scripts/update_template.js && node scripts/get-contributors.js", + "update": "node scripts/update_template.js && node scripts/get-contributors.js && node scripts/update_whos_using.js", "start": "node __sapper__/build", "test": "mocha -r esm test/**", "deploy": "make deploy" diff --git a/site/scripts/update_whos_using.js b/site/scripts/update_whos_using.js new file mode 100644 index 0000000000..131a162359 --- /dev/null +++ b/site/scripts/update_whos_using.js @@ -0,0 +1,12 @@ +const sh = require('shelljs'); + +sh.cd(__dirname + '/../'); + +// fetch community repo +sh.rm('-rf','scripts/community'); +sh.exec('npx degit sveltejs/community scripts/community'); + +// copy over relevant files +sh.cp('scripts/community/whos-using-svelte/WhosUsingSvelte.svelte', 'src/routes/_components/WhosUsingSvelte.svelte'); +sh.rm('-rf', 'static/organisations'); +sh.cp('-r', 'scripts/community/whos-using-svelte/organisations', 'static'); diff --git a/site/src/routes/_components/WhosUsingSvelte.svelte b/site/src/routes/_components/WhosUsingSvelte.svelte deleted file mode 100644 index 3288e79938..0000000000 --- a/site/src/routes/_components/WhosUsingSvelte.svelte +++ /dev/null @@ -1,95 +0,0 @@ -<!-- - Instructions for adding new logos: - - * Fork this repo, and clone your fork - * Create a branch called e.g. `add-myorganisation-logo` - * Add the logo to the `static/organisations` directory (preferably SVG) - * Add a new <a> tag in this component, in alphabetical order - * Create a pull request. Thanks! ---> - -<style> - .logos { - margin: 1em 0 0 0; - display: flex; - flex-wrap: wrap; - } - - a { - height: 40px; - margin: 0 0.5em 0.5em 0; - display: flex; - align-items: center; - border: 2px solid var(--second); - padding: 5px 10px; - border-radius: 20px; - color: var(--text); - } - - .add-yourself { - color: var(--prime); - } - - picture, - img { - height: 100%; - } - - @media (min-width: 540px) { - a { - height: 60px; - padding: 10px 20px; - border-radius: 30px; - } - } -</style> - -<div class="logos"> - <a target="_blank" rel="noopener" href="https://absoluteweb.com"><img src="organisations/absoluteweb.svg" alt="Absolute Web logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="https://bekchy.com"><img src="organisations/bekchy.png" alt="Bekchy logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="https://beyonk.com"><img src="organisations/beyonk.svg" alt="Beyonk logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="https://buydotstar.com"><img src="organisations/buydotstar.svg" alt="buy.* logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="https://cashfree.com/"><img src="organisations/cashfree.svg" alt="Cashfree logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="https://chess.com" style="background-color: rgb(49,46,43);"><img src="organisations/chess.svg" alt="Chess.com logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="https://comigosaude.com.br"><img src="organisations/comigo.svg" alt="Comigo logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="https://datawrapper.de"><img src="organisations/datawrapper.svg" alt="Datawrapper logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="https://db.nomics.world" style="background-color: rgb(15,39,47);"><picture><source type="image/webp" srcset="organisations/dbnomics.webp"><img src="organisations/dbnomics.jpg" alt="DBNomics logo" loading="lazy"></picture></a> - <a target="_blank" rel="noopener" href="https://deck.nl"><img src="organisations/deck.svg" alt="Deck logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="https://dextra.com.br/pt/"><img src="organisations/dextra.png" alt="Dextra logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="https://www.entriwise.com/"><img src="organisations/entriwise.png" alt="Entriwise logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="https://www.entur.org/about-entur/" style="background-color: rgb(25, 25, 84);"><img src="organisations/entur.svg" alt="Entur logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="https://from-now-on.com"><img src="organisations/from-now-on.png" alt="From-Now-On logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="https://fusioncharts.com"><img src="organisations/fusioncharts.svg" alt="FusionCharts logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="https://godaddy.com"><img src="organisations/godaddy.svg" alt="GoDaddy logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="https://www.grainger.com"><img src="organisations/grainger.svg" alt="Grainger logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="http://healthtree.org/"><img src="organisations/healthtree.png" alt="HealthTree logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="https://iota.org"><img src="organisations/iota.svg" alt="IOTA logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="https://itslearning.com"><img src="organisations/itslearning.svg" alt="itslearning logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="https://jacoux.com"><img src="organisations/jacoux.png" alt="Jacoux logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="https://jingmnt.co.za"><img src="organisations/jingmnt.png" alt="Jingmnt logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="https://www.mentorcv.com"><img src="organisations/mentorcv.png" alt="Mentor CV logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="https://www.metrovias.com.ar/"><img src="organisations/metrovias.svg" alt="Metrovias logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="http://mustlab.ru"><img src="organisations/mustlab.png" alt="Mustlab logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="https://www.nesta.org.uk"><img src="organisations/nesta.svg" alt="Nesta logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="https://www.nonkositelecoms.com"><img src="organisations/nonkosi.svg" alt="Nonkosi Telecoms logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="https://www.nzz.ch"><img src="organisations/nzz.svg" alt="Neue Zürcher Zeitung logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="https://nytimes.com"><img src="organisations/nyt.svg" alt="The New York Times logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="https://oberonspace.xyz"><img src="organisations/oberonspace.svg" alt="OberonSPACE logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="https://ofof.nl"><img src="organisations/ofof.png" alt="Ofof logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="https://openstate.eu"><img src="organisations/open-state-foundation.svg" alt="Open State Foundation logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="https://panascais.net"><img src="organisations/panascais.svg" alt="Panascais logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="https://pankod.com"><img src="organisations/pankod.svg" alt="Pankod logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="https://paperform.co"><img src="organisations/paperform.svg" alt="Paperform logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="https://razorpay.com"><img src="organisations/razorpay.svg" alt="Razorpay logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="https://sp.nl"><img src="organisations/socialist-party.svg" alt="Socialist Party logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="https://sqltribe.com"><img src="organisations/sqltribe.svg" alt="SQL Tribe logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="https://www.stone.co"><img src="organisations/stone.svg" alt="Stone Payments logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="https://www.strixengine.com"><img src="organisations/strixcloud.svg" alt="Strix Cloud logo" loading="lazy"><span>Strix Cloud</span></a> - <a target="_blank" rel="noopener" href="https://sucuri.net" style="background-color: rgb(93, 93, 93);"><img src="organisations/sucuri.png" alt="Sucuri logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="https://tsh.io"><img src="organisations/tsh.svg" alt="The Software House logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="https://thunderdome.dev"><img src="organisations/thunderdome.svg" alt="Thunderdome logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="https://m.tokopedia.com"><img src="organisations/tokopedia.svg" alt="Tokopedia logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="https://webdesq.net"><img src="organisations/webdesq.svg" alt="Webdesq logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="https://zevvle.com/"><img src="organisations/zevvle.svg" alt="Zevvle logo" loading="lazy"></a> - <a target="_blank" rel="noopener" href="https://github.com/sveltejs/svelte/blob/master/site/src/routes/_components/WhosUsingSvelte.svelte" class="add-yourself"><span>+ your company?</span></a> -</div> diff --git a/site/src/routes/blog/index.svelte b/site/src/routes/blog/index.svelte index 6f96731af0..92fc5ce96d 100644 --- a/site/src/routes/blog/index.svelte +++ b/site/src/routes/blog/index.svelte @@ -11,7 +11,7 @@ <svelte:head> <title>Blog • Svelte - + diff --git a/site/src/routes/examples/_TableOfContents.svelte b/site/src/routes/examples/_TableOfContents.svelte index 167e889e2a..d5a8b7240f 100644 --- a/site/src/routes/examples/_TableOfContents.svelte +++ b/site/src/routes/examples/_TableOfContents.svelte @@ -59,6 +59,7 @@ font-size: 1.6rem; align-items: center; justify-content: start; + padding: 0; } a:hover { diff --git a/site/src/template.html b/site/src/template.html index dc930a7f72..344684d766 100644 --- a/site/src/template.html +++ b/site/src/template.html @@ -15,6 +15,7 @@ + - logo colour - Created with Sketch. - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/static/organisations/godaddy.svg b/site/static/organisations/godaddy.svg deleted file mode 100644 index e753ceed7b..0000000000 --- a/site/static/organisations/godaddy.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/site/static/organisations/grainger.svg b/site/static/organisations/grainger.svg deleted file mode 100644 index af10fc52fe..0000000000 --- a/site/static/organisations/grainger.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/site/static/organisations/healthtree.png b/site/static/organisations/healthtree.png deleted file mode 100644 index ec3f219d1c..0000000000 Binary files a/site/static/organisations/healthtree.png and /dev/null differ diff --git a/site/static/organisations/iota.svg b/site/static/organisations/iota.svg deleted file mode 100644 index 1ab864a958..0000000000 --- a/site/static/organisations/iota.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/site/static/organisations/itslearning.svg b/site/static/organisations/itslearning.svg deleted file mode 100644 index 1d5fe6fcf4..0000000000 --- a/site/static/organisations/itslearning.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/site/static/organisations/jacoux.png b/site/static/organisations/jacoux.png deleted file mode 100644 index 6d3f52017c..0000000000 Binary files a/site/static/organisations/jacoux.png and /dev/null differ diff --git a/site/static/organisations/jingmnt.png b/site/static/organisations/jingmnt.png deleted file mode 100644 index 326f5f0bbf..0000000000 Binary files a/site/static/organisations/jingmnt.png and /dev/null differ diff --git a/site/static/organisations/mentorcv.png b/site/static/organisations/mentorcv.png deleted file mode 100644 index 17cf95a9ac..0000000000 Binary files a/site/static/organisations/mentorcv.png and /dev/null differ diff --git a/site/static/organisations/metrovias.svg b/site/static/organisations/metrovias.svg deleted file mode 100644 index a81394de59..0000000000 --- a/site/static/organisations/metrovias.svg +++ /dev/null @@ -1 +0,0 @@ -Asset 1 \ No newline at end of file diff --git a/site/static/organisations/mustlab.png b/site/static/organisations/mustlab.png deleted file mode 100644 index 4f3a29488b..0000000000 Binary files a/site/static/organisations/mustlab.png and /dev/null differ diff --git a/site/static/organisations/nesta.svg b/site/static/organisations/nesta.svg deleted file mode 100644 index 72f53e07b0..0000000000 --- a/site/static/organisations/nesta.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/site/static/organisations/nonkosi.svg b/site/static/organisations/nonkosi.svg deleted file mode 100644 index 8639c0ce38..0000000000 --- a/site/static/organisations/nonkosi.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/site/static/organisations/nyt.svg b/site/static/organisations/nyt.svg deleted file mode 100644 index 8735e53059..0000000000 --- a/site/static/organisations/nyt.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/site/static/organisations/nzz.svg b/site/static/organisations/nzz.svg deleted file mode 100644 index 70f9486263..0000000000 --- a/site/static/organisations/nzz.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/site/static/organisations/oberonspace.svg b/site/static/organisations/oberonspace.svg deleted file mode 100644 index 73fd68f8b5..0000000000 --- a/site/static/organisations/oberonspace.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/site/static/organisations/ofof.png b/site/static/organisations/ofof.png deleted file mode 100644 index 5ecde9b0fd..0000000000 Binary files a/site/static/organisations/ofof.png and /dev/null differ diff --git a/site/static/organisations/open-state-foundation.svg b/site/static/organisations/open-state-foundation.svg deleted file mode 100644 index f8c010e7e2..0000000000 --- a/site/static/organisations/open-state-foundation.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/site/static/organisations/panascais.svg b/site/static/organisations/panascais.svg deleted file mode 100644 index cb9e4f3afe..0000000000 --- a/site/static/organisations/panascais.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/site/static/organisations/pankod.svg b/site/static/organisations/pankod.svg deleted file mode 100644 index 34578a7739..0000000000 --- a/site/static/organisations/pankod.svg +++ /dev/null @@ -1,33 +0,0 @@ - - - - Dark - Created with Sketch. - - - - - - - - \ No newline at end of file diff --git a/site/static/organisations/paperform.svg b/site/static/organisations/paperform.svg deleted file mode 100644 index 3ee2540112..0000000000 --- a/site/static/organisations/paperform.svg +++ /dev/null @@ -1,26 +0,0 @@ - - - - Logo--gradient--shadow - Created with Sketch. - - - - - - - - - - - - - - \ No newline at end of file diff --git a/site/static/organisations/razorpay.svg b/site/static/organisations/razorpay.svg deleted file mode 100644 index 829adb319e..0000000000 --- a/site/static/organisations/razorpay.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/site/static/organisations/socialist-party.svg b/site/static/organisations/socialist-party.svg deleted file mode 100644 index 9dde31a5c0..0000000000 --- a/site/static/organisations/socialist-party.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/site/static/organisations/sqltribe.svg b/site/static/organisations/sqltribe.svg deleted file mode 100644 index 2a51dfc0a7..0000000000 --- a/site/static/organisations/sqltribe.svg +++ /dev/null @@ -1,106 +0,0 @@ - -image/svg+xml \ No newline at end of file diff --git a/site/static/organisations/stone.svg b/site/static/organisations/stone.svg deleted file mode 100644 index 0401dbb292..0000000000 --- a/site/static/organisations/stone.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/site/static/organisations/strixcloud.svg b/site/static/organisations/strixcloud.svg deleted file mode 100644 index 49fe96b061..0000000000 --- a/site/static/organisations/strixcloud.svg +++ /dev/null @@ -1,244 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/site/static/organisations/sucuri.png b/site/static/organisations/sucuri.png deleted file mode 100644 index a30b161139..0000000000 Binary files a/site/static/organisations/sucuri.png and /dev/null differ diff --git a/site/static/organisations/thunderdome.svg b/site/static/organisations/thunderdome.svg deleted file mode 100644 index 0a5d8ee055..0000000000 --- a/site/static/organisations/thunderdome.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/site/static/organisations/tokopedia.svg b/site/static/organisations/tokopedia.svg deleted file mode 100644 index d284f05d7b..0000000000 --- a/site/static/organisations/tokopedia.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/site/static/organisations/tsh.svg b/site/static/organisations/tsh.svg deleted file mode 100644 index 0b0388b500..0000000000 --- a/site/static/organisations/tsh.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/site/static/organisations/webdesq.svg b/site/static/organisations/webdesq.svg deleted file mode 100644 index c9f0012de0..0000000000 --- a/site/static/organisations/webdesq.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/site/static/organisations/zevvle.svg b/site/static/organisations/zevvle.svg deleted file mode 100644 index 45a26c7a7c..0000000000 --- a/site/static/organisations/zevvle.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts index 1a5d39ab28..9822529ece 100644 --- a/src/compiler/compile/Component.ts +++ b/src/compiler/compile/Component.ts @@ -472,7 +472,7 @@ export default class Component { if (variable.writable && !(variable.referenced || variable.referenced_from_script || variable.subscribable)) { this.warn(declarator, { code: `unused-export-let`, - message: `${this.name.name} has unused export property '${name}'. If it is for external reference only, please consider using \`export const '${name}'\`` + message: `${this.name.name} has unused export property '${name}'. If it is for external reference only, please consider using \`export const ${name}\`` }); } }); @@ -495,7 +495,7 @@ export default class Component { if (variable.writable && !(variable.referenced || variable.referenced_from_script || variable.subscribable)) { this.warn(specifier, { code: `unused-export-let`, - message: `${this.name.name} has unused export property '${specifier.exported.name}'. If it is for external reference only, please consider using \`export const '${specifier.exported.name}'\`` + message: `${this.name.name} has unused export property '${specifier.exported.name}'. If it is for external reference only, please consider using \`export const ${specifier.exported.name}\`` }); } } @@ -603,6 +603,7 @@ export default class Component { const { expression } = node.body; if (expression.type !== 'AssignmentExpression') return; + if (expression.left.type === 'MemberExpression') return; extract_names(expression.left).forEach(name => { if (!this.var_lookup.has(name) && name[0] !== '$') { diff --git a/src/compiler/compile/css/Selector.ts b/src/compiler/compile/css/Selector.ts index eecb0cd975..6bc046c93e 100644 --- a/src/compiler/compile/css/Selector.ts +++ b/src/compiler/compile/css/Selector.ts @@ -32,7 +32,7 @@ export default class Selector { } this.local_blocks = this.blocks.slice(0, i); - this.used = this.blocks[0].global; + this.used = this.local_blocks.length === 0; } apply(node: Element, stack: Element[]) { @@ -171,7 +171,7 @@ function apply_selector(blocks: Block[], node: Element, stack: Element[], to_enc if (ancestor_block.global) { continue; } - + for (const stack_node of stack) { if (block_might_apply_to_node(ancestor_block, stack_node) !== BlockAppliesToNode.NotPossible) { to_encapsulate.push({ node: stack_node, block: ancestor_block }); @@ -256,7 +256,7 @@ function test_attribute(operator, expected_value, case_insensitive, value) { } switch (operator) { case '=': return value === expected_value; - case '~=': return ` ${value} `.includes(` ${expected_value} `); + case '~=': return value.split(/\s/).includes(expected_value); case '|=': return `${value}-`.startsWith(`${expected_value}-`); case '^=': return value.startsWith(expected_value); case '$=': return value.endsWith(expected_value); @@ -295,7 +295,7 @@ function attribute_matches(node: CssNode, name: string, expected_value: string, // impossible to find out all combinations if (current_possible_values.has(UNKNOWN)) return true; - + if (prev_values.length > 0) { const start_with_space = []; const remaining = []; diff --git a/src/compiler/compile/css/Stylesheet.ts b/src/compiler/compile/css/Stylesheet.ts index 998a879687..246dab0f12 100644 --- a/src/compiler/compile/css/Stylesheet.ts +++ b/src/compiler/compile/css/Stylesheet.ts @@ -5,6 +5,7 @@ import Element from '../nodes/Element'; import { Ast, TemplateNode } from '../../interfaces'; import Component from '../Component'; import { CssNode } from './interfaces'; +import hash from "../utils/hash"; function remove_css_prefix(name: string): string { return name.replace(/^-((webkit)|(moz)|(o)|(ms))-/, ''); @@ -37,15 +38,6 @@ function minify_declarations( return c; } -// https://github.com/darkskyapp/string-hash/blob/master/index.js -function hash(str: string): string { - let hash = 5381; - let i = str.length; - - while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i); - return (hash >>> 0).toString(36); -} - class Rule { selectors: Selector[]; declarations: Declaration[]; diff --git a/src/compiler/compile/nodes/Binding.ts b/src/compiler/compile/nodes/Binding.ts index 28e6af5aa1..7d6fad0a81 100644 --- a/src/compiler/compile/nodes/Binding.ts +++ b/src/compiler/compile/nodes/Binding.ts @@ -50,6 +50,13 @@ export default class Binding extends Node { message: 'Cannot bind to a variable declared with the let: directive' }); } else if (this.is_contextual) { + if (scope.is_await(name)) { + component.error(this, { + code: 'invalid-binding', + message: 'Cannot bind to a variable declared with {#await ... then} or {:catch} blocks' + }); + } + scope.dependencies_for_name.get(name).forEach(name => { const variable = component.var_lookup.get(name); if (variable) { diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index a3b8dc7286..e8108858c5 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -151,6 +151,11 @@ export default class Element extends Node { } } + const has_let = info.attributes.some(node => node.type === 'Let'); + if (has_let) { + scope = scope.child(); + } + // Binding relies on Attribute, defer its evaluation const order = ['Binding']; // everything else is -1 info.attributes.sort((a, b) => order.indexOf(a.type) - order.indexOf(b.type)); @@ -181,9 +186,16 @@ export default class Element extends Node { this.handlers.push(new EventHandler(component, this, scope, node)); break; - case 'Let': - this.lets.push(new Let(component, this, scope, node)); + case 'Let': { + const l = new Let(component, this, scope, node); + this.lets.push(l); + const dependencies = new Set([l.name.name]); + + l.names.forEach(name => { + scope.add(name, dependencies, this); + }); break; + } case 'Transition': { @@ -202,20 +214,7 @@ export default class Element extends Node { } }); - if (this.lets.length > 0) { - this.scope = scope.child(); - - this.lets.forEach(l => { - const dependencies = new Set([l.name.name]); - - l.names.forEach(name => { - this.scope.add(name, dependencies, this); - }); - }); - } else { - this.scope = scope; - } - + this.scope = scope; this.children = map_children(component, this, this.scope, info.children); this.validate(); diff --git a/src/compiler/compile/nodes/EventHandler.ts b/src/compiler/compile/nodes/EventHandler.ts index 4242c82394..110542d0b8 100644 --- a/src/compiler/compile/nodes/EventHandler.ts +++ b/src/compiler/compile/nodes/EventHandler.ts @@ -53,13 +53,6 @@ export default class EventHandler extends Node { } const node = this.expression.node; - if (node.type === 'Identifier') { - return ( - this.component.node_for_declaration.get(node.name) && - this.component.var_lookup.get(node.name).reassigned - ); - } - if (/FunctionExpression/.test(node.type)) { return false; } diff --git a/src/compiler/compile/nodes/Head.ts b/src/compiler/compile/nodes/Head.ts index 2c08dcd595..53e76d7a4d 100644 --- a/src/compiler/compile/nodes/Head.ts +++ b/src/compiler/compile/nodes/Head.ts @@ -1,9 +1,11 @@ import Node from './shared/Node'; import map_children from './shared/map_children'; +import hash from '../utils/hash'; export default class Head extends Node { type: 'Head'; children: any[]; // TODO + id: string; constructor(component, parent, scope, info) { super(component, parent, scope, info); @@ -18,5 +20,9 @@ export default class Head extends Node { this.children = map_children(component, parent, scope, info.children.filter(child => { return (child.type !== 'Text' || /\S/.test(child.data)); })); + + if (this.children.length > 0) { + this.id = `svelte-${hash(this.component.source.slice(this.start, this.end))}`; + } } } diff --git a/src/compiler/compile/nodes/shared/TemplateScope.ts b/src/compiler/compile/nodes/shared/TemplateScope.ts index 5f30d0c883..4e087eedf5 100644 --- a/src/compiler/compile/nodes/shared/TemplateScope.ts +++ b/src/compiler/compile/nodes/shared/TemplateScope.ts @@ -42,4 +42,9 @@ export default class TemplateScope { const owner = this.get_owner(name); return owner && (owner.type === 'Element' || owner.type === 'InlineComponent'); } + + is_await(name: string) { + const owner = this.get_owner(name); + return owner && (owner.type === 'ThenBlock' || owner.type === 'CatchBlock'); + } } diff --git a/src/compiler/compile/render_dom/Block.ts b/src/compiler/compile/render_dom/Block.ts index c8fa884721..62bdc5bdd9 100644 --- a/src/compiler/compile/render_dom/Block.ts +++ b/src/compiler/compile/render_dom/Block.ts @@ -160,6 +160,9 @@ export default class Block { }); this.has_update_method = true; + if (this.parent) { + this.parent.add_dependencies(dependencies); + } } add_element( @@ -450,7 +453,7 @@ export default class Block { this.add_variable(dispose); if (this.event_listeners.length === 1) { - this.chunks.hydrate.push( + this.chunks.mount.push( b`${dispose} = ${this.event_listeners[0]};` ); @@ -458,7 +461,7 @@ export default class Block { b`${dispose}();` ); } else { - this.chunks.hydrate.push(b` + this.chunks.mount.push(b` ${dispose} = [ ${this.event_listeners} ]; diff --git a/src/compiler/compile/render_dom/Renderer.ts b/src/compiler/compile/render_dom/Renderer.ts index 046a90b684..fbb0c76d7d 100644 --- a/src/compiler/compile/render_dom/Renderer.ts +++ b/src/compiler/compile/render_dom/Renderer.ts @@ -161,11 +161,14 @@ export default class Renderer { } if ( - variable && - !variable.referenced && - !variable.is_reactive_dependency && - !variable.export_name && - !name.startsWith('$$') + variable && ( + variable.module || ( + !variable.referenced && + !variable.is_reactive_dependency && + !variable.export_name && + !name.startsWith('$$') + ) + ) ) { return value || name; } @@ -194,7 +197,7 @@ export default class Renderer { return filtered .map(n => x`$$invalidate(${this.context_lookup.get(n).index}, ${n})`) - .reduce((lhs, rhs) => x`${lhs}, ${rhs}}`); + .reduce((lhs, rhs) => x`${lhs}, ${rhs}`); } dirty(names, is_reactive_declaration = false): Expression { diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index b87fe05ef7..cf97d22218 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -422,9 +422,9 @@ export default function dom( ${set && b`$$self.$set = ${set};`} - ${capture_state && x`$$self.$capture_state = ${capture_state};`} + ${capture_state && b`$$self.$capture_state = ${capture_state};`} - ${inject_state && x`$$self.$inject_state = ${inject_state};`} + ${inject_state && b`$$self.$inject_state = ${inject_state};`} ${injected.map(name => b`let ${name};`)} diff --git a/src/compiler/compile/render_dom/invalidate.ts b/src/compiler/compile/render_dom/invalidate.ts index 65fb73afc3..20aff05d1e 100644 --- a/src/compiler/compile/render_dom/invalidate.ts +++ b/src/compiler/compile/render_dom/invalidate.ts @@ -49,7 +49,7 @@ export function invalidate(renderer: Renderer, scope: Scope, node: Node, names: const pass_value = ( extra_args.length > 0 || (node.type === 'AssignmentExpression' && node.left.type !== 'Identifier') || - (node.type === 'UpdateExpression' && !node.prefix) + (node.type === 'UpdateExpression' && (!node.prefix || node.argument.type !== 'Identifier')) ); if (pass_value) { @@ -67,7 +67,7 @@ export function invalidate(renderer: Renderer, scope: Scope, node: Node, names: if (head.subscribable && head.reassigned) { const subscribe = `$$subscribe_${head.name}`; - invalidate = x`${subscribe}(${invalidate})}`; + invalidate = x`${subscribe}(${invalidate})`; } return invalidate; diff --git a/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts b/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts index 25c5312e65..12b7fc2cef 100644 --- a/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts @@ -205,7 +205,7 @@ export default class AwaitBlockWrapper extends Wrapper { } else { const #child_ctx = #ctx.slice(); - ${this.node.value && x`#child_ctx[${value_index}] = ${info}.resolved;`} + ${this.node.value && b`#child_ctx[${value_index}] = ${info}.resolved;`} ${info}.block.p(#child_ctx, #dirty); } `); @@ -219,7 +219,7 @@ export default class AwaitBlockWrapper extends Wrapper { block.chunks.update.push(b` { const #child_ctx = #ctx.slice(); - ${this.node.value && x`#child_ctx[${value_index}] = ${info}.resolved;`} + ${this.node.value && b`#child_ctx[${value_index}] = ${info}.resolved;`} ${info}.block.p(#child_ctx, #dirty); } `); diff --git a/src/compiler/compile/render_dom/wrappers/Body.ts b/src/compiler/compile/render_dom/wrappers/Body.ts index e16ebc25bd..d80ef6c4a0 100644 --- a/src/compiler/compile/render_dom/wrappers/Body.ts +++ b/src/compiler/compile/render_dom/wrappers/Body.ts @@ -1,26 +1,23 @@ import Block from '../Block'; import Wrapper from './shared/Wrapper'; -import { b } from 'code-red'; +import { x } from 'code-red'; import Body from '../../nodes/Body'; import { Identifier } from 'estree'; import EventHandler from './Element/EventHandler'; +import add_event_handlers from './shared/add_event_handlers'; +import { TemplateNode } from '../../../interfaces'; +import Renderer from '../Renderer'; export default class BodyWrapper extends Wrapper { node: Body; + handlers: EventHandler[]; - render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) { - this.node.handlers - .map(handler => new EventHandler(handler, this)) - .forEach(handler => { - const snippet = handler.get_snippet(block); - - block.chunks.init.push(b` - @_document.body.addEventListener("${handler.node.name}", ${snippet}); - `); + constructor(renderer: Renderer, block: Block, parent: Wrapper, node: TemplateNode) { + super(renderer, block, parent, node); + this.handlers = this.node.handlers.map(handler => new EventHandler(handler, this)); + } - block.chunks.destroy.push(b` - @_document.body.removeEventListener("${handler.node.name}", ${snippet}); - `); - }); + render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) { + add_event_handlers(block, x`@_document.body`, this.handlers); } } diff --git a/src/compiler/compile/render_dom/wrappers/EachBlock.ts b/src/compiler/compile/render_dom/wrappers/EachBlock.ts index 5b13b486e6..f0dfa5fbcc 100644 --- a/src/compiler/compile/render_dom/wrappers/EachBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/EachBlock.ts @@ -204,6 +204,9 @@ export default class EachBlockWrapper extends Wrapper { const snippet = this.node.expression.manipulate(block); block.chunks.init.push(b`let ${this.vars.each_block_value} = ${snippet};`); + if (this.renderer.options.dev) { + block.chunks.init.push(b`@validate_each_argument(${this.vars.each_block_value});`); + } // TODO which is better — Object.create(array) or array.slice()? renderer.blocks.push(b` @@ -264,10 +267,23 @@ export default class EachBlockWrapper extends Wrapper { block.chunks.init.push(b` if (!${this.vars.data_length}) { ${each_block_else} = ${this.else.block.name}(#ctx); + } + `); + + block.chunks.create.push(b` + if (${each_block_else}) { ${each_block_else}.c(); } `); + if (this.renderer.options.hydratable) { + block.chunks.claim.push(b` + if (${each_block_else}) { + ${each_block_else}.l(${parent_nodes}); + } + `); + } + block.chunks.mount.push(b` if (${each_block_else}) { ${each_block_else}.m(${initial_mount_node}, ${initial_anchor_node}); @@ -361,6 +377,7 @@ export default class EachBlockWrapper extends Wrapper { block.chunks.init.push(b` const ${get_key} = #ctx => ${this.node.key.manipulate(block)}; + ${this.renderer.options.dev && b`@validate_each_keys(#ctx, ${this.vars.each_block_value}, ${this.vars.get_each_context}, ${get_key});`} for (let #i = 0; #i < ${data_length}; #i += 1) { let child_ctx = ${this.vars.get_each_context}(#ctx, ${this.vars.each_block_value}, #i); let key = ${get_key}(child_ctx); @@ -398,15 +415,26 @@ export default class EachBlockWrapper extends Wrapper { ? `@outro_and_destroy_block` : `@destroy_block`; - block.chunks.update.push(b` - const ${this.vars.each_block_value} = ${snippet}; + const all_dependencies = new Set(this.block.dependencies); // TODO should be dynamic deps only + this.node.expression.dynamic_dependencies().forEach((dependency: string) => { + all_dependencies.add(dependency); + }); - ${this.block.has_outros && b`@group_outros();`} - ${this.node.has_animation && b`for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].r();`} - ${iterations} = @update_keyed_each(${iterations}, #dirty, ${get_key}, ${dynamic ? 1 : 0}, #ctx, ${this.vars.each_block_value}, ${lookup}, ${update_mount_node}, ${destroy}, ${create_each_block}, ${update_anchor_node}, ${this.vars.get_each_context}); - ${this.node.has_animation && b`for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].a();`} - ${this.block.has_outros && b`@check_outros();`} - `); + if (all_dependencies.size) { + block.chunks.update.push(b` + if (${block.renderer.dirty(Array.from(all_dependencies))}) { + const ${this.vars.each_block_value} = ${snippet}; + ${this.renderer.options.dev && b`@validate_each_argument(${this.vars.each_block_value});`} + + ${this.block.has_outros && b`@group_outros();`} + ${this.node.has_animation && b`for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].r();`} + ${this.renderer.options.dev && b`@validate_each_keys(#ctx, ${this.vars.each_block_value}, ${this.vars.get_each_context}, ${get_key});`} + ${iterations} = @update_keyed_each(${iterations}, #dirty, ${get_key}, ${dynamic ? 1 : 0}, #ctx, ${this.vars.each_block_value}, ${lookup}, ${update_mount_node}, ${destroy}, ${create_each_block}, ${update_anchor_node}, ${this.vars.get_each_context}); + ${this.node.has_animation && b`for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].a();`} + ${this.block.has_outros && b`@check_outros();`} + } + `); + } if (this.block.has_outros) { block.chunks.outro.push(b` @@ -548,6 +576,7 @@ export default class EachBlockWrapper extends Wrapper { const update = b` ${!this.block.has_update_method && b`const #old_length = ${this.vars.each_block_value}.length;`} ${this.vars.each_block_value} = ${snippet}; + ${this.renderer.options.dev && b`@validate_each_argument(${this.vars.each_block_value});`} let #i; for (#i = ${start}; #i < ${data_length}; #i += 1) { diff --git a/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts b/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts index ecddaaebaa..85f252f57e 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts @@ -39,20 +39,25 @@ export default class AttributeWrapper { } } - render(block: Block) { + is_indirectly_bound_value() { const element = this.parent; const name = fix_attribute_casing(this.node.name); - - const metadata = this.get_metadata(); - - const is_indirectly_bound_value = - name === 'value' && + return name === 'value' && (element.node.name === 'option' || // TODO check it's actually bound (element.node.name === 'input' && - element.node.bindings.find( + element.node.bindings.some( (binding) => /checked|group/.test(binding.name) ))); + } + + render(block: Block) { + const element = this.parent; + const name = fix_attribute_casing(this.node.name); + + const metadata = this.get_metadata(); + + const is_indirectly_bound_value = this.is_indirectly_bound_value(); const property_name = is_indirectly_bound_value ? '__value' diff --git a/src/compiler/compile/render_dom/wrappers/Element/Binding.ts b/src/compiler/compile/render_dom/wrappers/Element/Binding.ts index 7244d9203d..eca6e9d325 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/Binding.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/Binding.ts @@ -97,7 +97,7 @@ export default class BindingWrapper { const type = parent.node.get_static_attribute_value('type'); if (type === null || type === "" || type === "text" || type === "email" || type === "password") { - update_conditions.push(x`(${parent.var}.${this.node.name} !== ${this.snippet})`); + update_conditions.push(x`${parent.var}.${this.node.name} !== ${this.snippet}`); } } diff --git a/src/compiler/compile/render_dom/wrappers/Element/EventHandler.ts b/src/compiler/compile/render_dom/wrappers/Element/EventHandler.ts index 03183ee576..157e186ea6 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/EventHandler.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/EventHandler.ts @@ -2,6 +2,7 @@ import EventHandler from '../../../nodes/EventHandler'; import Wrapper from '../shared/Wrapper'; import Block from '../../Block'; import { b, x, p } from 'code-red'; +import { Expression } from 'estree'; const TRUE = x`true`; const FALSE = x`false`; @@ -35,7 +36,7 @@ export default class EventHandlerWrapper { return snippet; } - render(block: Block, target: string) { + render(block: Block, target: string | Expression) { let snippet = this.get_snippet(block); if (this.node.modifiers.has('preventDefault')) snippet = x`@prevent_default(${snippet})`; diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index ef33022402..9291f329b6 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -415,7 +415,7 @@ export default class ElementWrapper extends Wrapper { const is = this.attributes.find(attr => attr.node.name === 'is'); if (is) { - return x`@element_is("${name}", ${is.render_chunks(block).reduce((lhs, rhs) => x`${lhs} + ${rhs}`)});`; + return x`@element_is("${name}", ${is.render_chunks(block).reduce((lhs, rhs) => x`${lhs} + ${rhs}`)})`; } return x`@element("${name}")`; @@ -632,7 +632,7 @@ export default class ElementWrapper extends Wrapper { add_this_binding(block: Block, this_binding: Binding) { const { renderer } = this; - + renderer.component.has_reactive_assignments = true; const binding_callback = bind_this(renderer.component, block, this_binding.node, this.var); @@ -679,10 +679,10 @@ export default class ElementWrapper extends Wrapper { updates.push(condition ? x`${condition} && ${snippet}` : snippet); } else { const metadata = attr.get_metadata(); - const snippet = x`{ ${ - (metadata && metadata.property_name) || - fix_attribute_casing(attr.node.name) - }: ${attr.get_value(block)} }`; + const name = attr.is_indirectly_bound_value() + ? '__value' + : (metadata && metadata.property_name) || fix_attribute_casing(attr.node.name); + const snippet = x`{ ${name}: ${attr.get_value(block)} }`; initial_props.push(snippet); updates.push(condition ? x`${condition} && ${snippet}` : snippet); diff --git a/src/compiler/compile/render_dom/wrappers/Head.ts b/src/compiler/compile/render_dom/wrappers/Head.ts index 188c26931a..e0b723d6dd 100644 --- a/src/compiler/compile/render_dom/wrappers/Head.ts +++ b/src/compiler/compile/render_dom/wrappers/Head.ts @@ -3,11 +3,12 @@ import Renderer from '../Renderer'; import Block from '../Block'; import Head from '../../nodes/Head'; import FragmentWrapper from './Fragment'; -import { x } from 'code-red'; +import { x, b } from 'code-red'; import { Identifier } from 'estree'; export default class HeadWrapper extends Wrapper { fragment: FragmentWrapper; + node: Head; constructor( renderer: Renderer, @@ -32,6 +33,18 @@ export default class HeadWrapper extends Wrapper { } render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) { - this.fragment.render(block, x`@_document.head` as unknown as Identifier, x`#nodes` as unknown as Identifier); + let nodes; + if (this.renderer.options.hydratable && this.fragment.nodes.length) { + nodes = block.get_unique_name('head_nodes'); + block.chunks.claim.push(b`const ${nodes} = @query_selector_all('[data-svelte="${this.node.id}"]', @_document.head);`); + } + + this.fragment.render(block, x`@_document.head` as unknown as Identifier, nodes); + + if (nodes && this.renderer.options.hydratable) { + block.chunks.claim.push( + b`${nodes}.forEach(@detach);` + ); + } } } diff --git a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts index 631c172576..f1c57ed094 100644 --- a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts +++ b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts @@ -106,11 +106,28 @@ export default class InlineComponentWrapper extends Wrapper { block.add_outro(); } + warn_if_reactive() { + const { name } = this.node; + const variable = this.renderer.component.var_lookup.get(name); + if (!variable) { + return; + } + + if (variable.reassigned || variable.export_name || variable.is_reactive_dependency) { + this.renderer.component.warn(this.node, { + code: 'reactive-component', + message: `<${name}/> will not be reactive if ${name} changes. Use if you want this reactivity.`, + }); + } + } + render( block: Block, parent_node: Identifier, parent_nodes: Identifier ) { + this.warn_if_reactive(); + const { renderer } = this; const { component } = renderer; diff --git a/src/compiler/compile/render_dom/wrappers/MustacheTag.ts b/src/compiler/compile/render_dom/wrappers/MustacheTag.ts index b5ac6505f8..ea6634ec80 100644 --- a/src/compiler/compile/render_dom/wrappers/MustacheTag.ts +++ b/src/compiler/compile/render_dom/wrappers/MustacheTag.ts @@ -17,7 +17,7 @@ export default class MustacheTagWrapper extends Tag { render(block: Block, parent_node: Identifier, parent_nodes: Identifier) { const { init } = this.rename_this_method( block, - value => x`@set_data(${this.var}, ${value});` + value => x`@set_data(${this.var}, ${value})` ); block.add_element( diff --git a/src/compiler/compile/render_dom/wrappers/RawMustacheTag.ts b/src/compiler/compile/render_dom/wrappers/RawMustacheTag.ts index c6ddde5da2..3b13e6c68a 100644 --- a/src/compiler/compile/render_dom/wrappers/RawMustacheTag.ts +++ b/src/compiler/compile/render_dom/wrappers/RawMustacheTag.ts @@ -48,7 +48,7 @@ export default class RawMustacheTagWrapper extends Tag { const { init } = this.rename_this_method( block, - content => x`${html_tag}.p(${content});` + content => x`${html_tag}.p(${content})` ); const update_anchor = in_head ? 'null' : needs_anchor ? html_anchor : this.next ? this.next.var : 'null'; diff --git a/src/compiler/compile/render_dom/wrappers/shared/add_event_handlers.ts b/src/compiler/compile/render_dom/wrappers/shared/add_event_handlers.ts index 23a37715cc..99b8080b17 100644 --- a/src/compiler/compile/render_dom/wrappers/shared/add_event_handlers.ts +++ b/src/compiler/compile/render_dom/wrappers/shared/add_event_handlers.ts @@ -1,9 +1,10 @@ import Block from '../../Block'; import EventHandler from '../Element/EventHandler'; +import { Expression } from 'estree'; export default function add_event_handlers( block: Block, - target: string, + target: string | Expression, handlers: EventHandler[] ) { handlers.forEach(handler => add_event_handler(block, target, handler)); @@ -11,7 +12,7 @@ export default function add_event_handlers( export function add_event_handler( block: Block, - target: string, + target: string | Expression, handler: EventHandler ) { handler.render(block, target); diff --git a/src/compiler/compile/render_ssr/Renderer.ts b/src/compiler/compile/render_ssr/Renderer.ts index 00a7ee2fb5..fb9216327c 100644 --- a/src/compiler/compile/render_ssr/Renderer.ts +++ b/src/compiler/compile/render_ssr/Renderer.ts @@ -41,6 +41,7 @@ const handlers: Record = { export interface RenderOptions extends CompileOptions{ locate: (c: number) => { line: number; column: number }; + head_id?: string; } export default class Renderer { diff --git a/src/compiler/compile/render_ssr/handlers/AwaitBlock.ts b/src/compiler/compile/render_ssr/handlers/AwaitBlock.ts index 6dd38bfd2e..f637826280 100644 --- a/src/compiler/compile/render_ssr/handlers/AwaitBlock.ts +++ b/src/compiler/compile/render_ssr/handlers/AwaitBlock.ts @@ -12,9 +12,9 @@ export default function(node: AwaitBlock, renderer: Renderer, options: RenderOpt const then = renderer.pop(); renderer.add_expression(x` - (function(__value) { + function(__value) { if (@is_promise(__value)) return ${pending}; return (function(${node.value}) { return ${then}; }(__value)); - }(${node.expression.node})) + }(${node.expression.node}) `); } diff --git a/src/compiler/compile/render_ssr/handlers/Element.ts b/src/compiler/compile/render_ssr/handlers/Element.ts index 81b8801686..6e6a61974a 100644 --- a/src/compiler/compile/render_ssr/handlers/Element.ts +++ b/src/compiler/compile/render_ssr/handlers/Element.ts @@ -65,7 +65,7 @@ export default function(node: Element, renderer: Renderer, options: RenderOption } }); - renderer.add_expression(x`@spread([${args}], ${class_expression});`); + renderer.add_expression(x`@spread([${args}], ${class_expression})`); } else { let add_class_attribute = !!class_expression; node.attributes.forEach(attribute => { @@ -124,6 +124,10 @@ export default function(node: Element, renderer: Renderer, options: RenderOption } }); + if (options.hydratable && options.head_id) { + renderer.add_string(` data-svelte="${options.head_id}"`); + } + renderer.add_string('>'); if (node_contents !== undefined) { diff --git a/src/compiler/compile/render_ssr/handlers/Head.ts b/src/compiler/compile/render_ssr/handlers/Head.ts index d457942922..cf1e6bd555 100644 --- a/src/compiler/compile/render_ssr/handlers/Head.ts +++ b/src/compiler/compile/render_ssr/handlers/Head.ts @@ -3,9 +3,14 @@ import Head from '../../nodes/Head'; import { x } from 'code-red'; export default function(node: Head, renderer: Renderer, options: RenderOptions) { + const head_options = { + ...options, + head_id: node.id + }; + renderer.push(); - renderer.render(node.children, options); + renderer.render(node.children, head_options); const result = renderer.pop(); - renderer.add_expression(x`($$result.head += ${result}, "")`); + renderer.add_expression(x`$$result.head += ${result}, ""`); } diff --git a/src/compiler/compile/render_ssr/handlers/Title.ts b/src/compiler/compile/render_ssr/handlers/Title.ts index 62d49d461a..c4c7897185 100644 --- a/src/compiler/compile/render_ssr/handlers/Title.ts +++ b/src/compiler/compile/render_ssr/handlers/Title.ts @@ -1,10 +1,16 @@ import Renderer, { RenderOptions } from '../Renderer'; import Title from '../../nodes/Title'; +import { x } from 'code-red'; export default function(node: Title, renderer: Renderer, options: RenderOptions) { + renderer.push(); + renderer.add_string(``); renderer.render(node.children, options); renderer.add_string(``); + const result = renderer.pop(); + + renderer.add_expression(x`$$result.title = ${result}, ""`); } diff --git a/src/compiler/compile/utils/__test__.ts b/src/compiler/compile/utils/__test__.ts index 60ad681b47..7777bc6afb 100644 --- a/src/compiler/compile/utils/__test__.ts +++ b/src/compiler/compile/utils/__test__.ts @@ -10,7 +10,7 @@ describe('get_name_from_filename', () => { assert.equal(get_name_from_filename('path/to/Widget/index.svelte'), 'Widget'); }); - it('handles unusual filenames', () => { - assert.equal(get_name_from_filename('path/to/[...parts].svelte'), 'Parts'); + it('handles Windows filenames', () => { + assert.equal(get_name_from_filename('path\\to\\Widget.svelte'), 'Widget'); }); }); diff --git a/src/compiler/compile/utils/hash.ts b/src/compiler/compile/utils/hash.ts new file mode 100644 index 0000000000..7ac892611b --- /dev/null +++ b/src/compiler/compile/utils/hash.ts @@ -0,0 +1,8 @@ +// https://github.com/darkskyapp/string-hash/blob/master/index.js +export default function hash(str: string): string { + let hash = 5381; + let i = str.length; + + while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i); + return (hash >>> 0).toString(36); +} \ No newline at end of file diff --git a/src/compiler/parse/acorn.ts b/src/compiler/parse/acorn.ts index 30ab3c398c..d14721cdab 100644 --- a/src/compiler/parse/acorn.ts +++ b/src/compiler/parse/acorn.ts @@ -1,14 +1,13 @@ -import * as acorn from 'acorn'; +import { Node } from 'acorn'; +import * as code_red from 'code-red'; -const Parser = acorn.Parser; - -export const parse = (source: string) => Parser.parse(source, { +export const parse = (source: string): Node => code_red.parse(source, { sourceType: 'module', ecmaVersion: 11, locations: true }); -export const parse_expression_at = (source: string, index: number) => Parser.parseExpressionAt(source, index, { +export const parse_expression_at = (source: string, index: number): Node => code_red.parseExpressionAt(source, index, { ecmaVersion: 11, locations: true }); \ No newline at end of file diff --git a/src/compiler/parse/index.ts b/src/compiler/parse/index.ts index e552374698..a809eeebeb 100644 --- a/src/compiler/parse/index.ts +++ b/src/compiler/parse/index.ts @@ -141,7 +141,7 @@ export class Parser { return result; } - read_identifier() { + read_identifier(allow_reserved = false) { const start = this.index; let i = this.index; @@ -160,7 +160,7 @@ export class Parser { const identifier = this.template.slice(this.index, this.index = i); - if (reserved.has(identifier)) { + if (!allow_reserved && reserved.has(identifier)) { this.error({ code: `unexpected-reserved-word`, message: `'${identifier}' is a reserved word in JavaScript and cannot be used here` diff --git a/src/compiler/parse/read/context.ts b/src/compiler/parse/read/context.ts index 0db0c676a3..fe666467f8 100644 --- a/src/compiler/parse/read/context.ts +++ b/src/compiler/parse/read/context.ts @@ -1,4 +1,5 @@ import { Parser } from '../index'; +import { reserved } from '../../utils/names'; interface Identifier { start: number; @@ -116,8 +117,11 @@ export default function read_context(parser: Parser) { break; } + // TODO: DRY this out somehow + // We don't know whether we want to allow reserved words until we see whether there's a ':' after it + // Probably ideally we'd use Acorn to do all of this const start = parser.index; - const name = parser.read_identifier(); + const name = parser.read_identifier(true); const key: Identifier = { start, end: parser.index, @@ -126,9 +130,19 @@ export default function read_context(parser: Parser) { }; parser.allow_whitespace(); - const value = parser.eat(':') - ? (parser.allow_whitespace(), read_context(parser)) - : key; + let value: Context; + if (parser.eat(':')) { + parser.allow_whitespace(); + value = read_context(parser); + } else { + if (reserved.has(name)) { + parser.error({ + code: `unexpected-reserved-word`, + message: `'${name}' is a reserved word in JavaScript and cannot be used here` + }, start); + } + value = key; + } const property: Property = { start, diff --git a/src/compiler/parse/read/expression.ts b/src/compiler/parse/read/expression.ts index c8e955b719..f0f4e15c7a 100644 --- a/src/compiler/parse/read/expression.ts +++ b/src/compiler/parse/read/expression.ts @@ -1,37 +1,9 @@ import { parse_expression_at } from '../acorn'; import { Parser } from '../index'; -import { Identifier, Node, SimpleLiteral } from 'estree'; +import { Node } from 'estree'; import { whitespace } from '../../utils/patterns'; -const literals = new Map([['true', true], ['false', false], ['null', null]]); - export default function read_expression(parser: Parser): Node { - const start = parser.index; - - const name = parser.read_until(/\s*}/); - if (name && /^[a-z]+$/.test(name)) { - const end = start + name.length; - - if (literals.has(name)) { - return { - type: 'Literal', - start, - end, - value: literals.get(name), - raw: name, - } as SimpleLiteral; - } - - return { - type: 'Identifier', - start, - end: start + name.length, - name, - } as Identifier; - } - - parser.index = start; - try { const node = parse_expression_at(parser.template, parser.index); diff --git a/src/compiler/parse/state/mustache.ts b/src/compiler/parse/state/mustache.ts index 140e722b10..e5e365dddf 100644 --- a/src/compiler/parse/state/mustache.ts +++ b/src/compiler/parse/state/mustache.ts @@ -3,6 +3,7 @@ import read_expression from '../read/expression'; import { closing_tag_omitted } from '../utils/html'; import { whitespace } from '../../utils/patterns'; import { trim_start, trim_end } from '../../utils/trim'; +import { to_string } from '../utils/node'; import { Parser } from '../index'; import { TemplateNode } from '../../interfaces'; @@ -106,11 +107,14 @@ export default function mustache(parser: Parser) { // :else if if (parser.eat('if')) { const block = parser.current(); - if (block.type !== 'IfBlock') + if (block.type !== 'IfBlock') { parser.error({ code: `invalid-elseif-placement`, - message: 'Cannot have an {:else if ...} block outside an {#if ...} block' + message: parser.stack.some(block => block.type === 'IfBlock') + ? `Expected to close ${to_string(block)} before seeing {:else if ...} block` + : `Cannot have an {:else if ...} block outside an {#if ...} block` }); + } parser.require_whitespace(); @@ -144,7 +148,9 @@ export default function mustache(parser: Parser) { if (block.type !== 'IfBlock' && block.type !== 'EachBlock') { parser.error({ code: `invalid-else-placement`, - message: 'Cannot have an {:else} block outside an {#if ...} or {#each ...} block' + message: parser.stack.some(block => block.type === 'IfBlock' || block.type === 'EachBlock') + ? `Expected to close ${to_string(block)} before seeing {:else} block` + : `Cannot have an {:else} block outside an {#if ...} or {#each ...} block` }); } @@ -168,14 +174,18 @@ export default function mustache(parser: Parser) { if (block.type !== 'PendingBlock') { parser.error({ code: `invalid-then-placement`, - message: 'Cannot have an {:then} block outside an {#await ...} block' + message: parser.stack.some(block => block.type === 'PendingBlock') + ? `Expected to close ${to_string(block)} before seeing {:then} block` + : `Cannot have an {:then} block outside an {#await ...} block` }); } } else { if (block.type !== 'ThenBlock' && block.type !== 'PendingBlock') { parser.error({ code: `invalid-catch-placement`, - message: 'Cannot have an {:catch} block outside an {#await ...} block' + message: parser.stack.some(block => block.type === 'ThenBlock' || block.type === 'PendingBlock') + ? `Expected to close ${to_string(block)} before seeing {:catch} block` + : `Cannot have an {:catch} block outside an {#await ...} block` }); } } diff --git a/src/compiler/parse/state/tag.ts b/src/compiler/parse/state/tag.ts index c7e761afc2..40bb01de9b 100644 --- a/src/compiler/parse/state/tag.ts +++ b/src/compiler/parse/state/tag.ts @@ -288,6 +288,16 @@ function read_tag_name(parser: Parser) { function read_attribute(parser: Parser, unique_names: Set) { const start = parser.index; + function check_unique(name: string) { + if (unique_names.has(name)) { + parser.error({ + code: `duplicate-attribute`, + message: 'Attributes need to be unique' + }, start); + } + unique_names.add(name); + } + if (parser.eat('{')) { parser.allow_whitespace(); @@ -310,6 +320,8 @@ function read_attribute(parser: Parser, unique_names: Set) { parser.allow_whitespace(); parser.eat('}', true); + check_unique(name); + return { start, end: parser.index, @@ -341,17 +353,6 @@ function read_attribute(parser: Parser, unique_names: Set) { const colon_index = name.indexOf(':'); const type = colon_index !== -1 && get_directive_type(name.slice(0, colon_index)); - if (unique_names.has(name)) { - parser.error({ - code: `duplicate-attribute`, - message: 'Attributes need to be unique' - }, start); - } - - if (type !== "EventHandler") { - unique_names.add(name); - } - let value: any[] | true = true; if (parser.eat('=')) { parser.allow_whitespace(); @@ -367,6 +368,12 @@ function read_attribute(parser: Parser, unique_names: Set) { if (type) { const [directive_name, ...modifiers] = name.slice(colon_index + 1).split('|'); + if (type === 'Binding' && directive_name !== 'this') { + check_unique(directive_name); + } else if (type !== 'EventHandler') { + check_unique(name); + } + if (type === 'Ref') { parser.error({ code: `invalid-ref-directive`, @@ -410,6 +417,8 @@ function read_attribute(parser: Parser, unique_names: Set) { return directive; } + check_unique(name); + return { start, end, diff --git a/src/compiler/parse/utils/node.ts b/src/compiler/parse/utils/node.ts new file mode 100644 index 0000000000..0d39529b59 --- /dev/null +++ b/src/compiler/parse/utils/node.ts @@ -0,0 +1,30 @@ +import { TemplateNode } from '../../interfaces'; + +export function to_string(node: TemplateNode) { + switch (node.type) { + case 'IfBlock': + return '{#if} block'; + case 'ThenBlock': + return '{:then} block'; + case 'ElseBlock': + return '{:else} block'; + case 'PendingBlock': + case 'AwaitBlock': + return '{#await} block'; + case 'CatchBlock': + return '{:catch} block'; + case 'EachBlock': + return '{#each} block'; + case 'RawMustacheTag': + return '{@html} block'; + case 'DebugTag': + return '{@debug} block'; + case 'Element': + case 'InlineComponent': + case 'Slot': + case 'Title': + return `<${node.name}> tag`; + default: + return node.type; + } +} diff --git a/src/compiler/utils/names.ts b/src/compiler/utils/names.ts index 88d4d4a319..02cc12e087 100644 --- a/src/compiler/utils/names.ts +++ b/src/compiler/utils/names.ts @@ -5,6 +5,8 @@ export const globals = new Set([ 'alert', 'Array', 'Boolean', + 'clearInterval', + 'clearTimeout', 'confirm', 'console', 'Date', @@ -16,6 +18,9 @@ export const globals = new Set([ 'Error', 'EvalError', 'Event', + 'fetch', + 'global', + 'globalThis', 'history', 'Infinity', 'InternalError', @@ -41,11 +46,14 @@ export const globals = new Set([ 'RegExp', 'sessionStorage', 'Set', + 'setInterval', + 'setTimeout', 'String', 'SyntaxError', 'TypeError', 'undefined', 'URIError', + 'URL', 'window' ]); diff --git a/src/runtime/internal/Component.ts b/src/runtime/internal/Component.ts index c1d2f80101..10588a0804 100644 --- a/src/runtime/internal/Component.ts +++ b/src/runtime/internal/Component.ts @@ -127,7 +127,8 @@ export function init(component, options, instance, create_fragment, not_equal, p let ready = false; $$.ctx = instance - ? instance(component, prop_values, (i, ret, value = ret) => { + ? instance(component, prop_values, (i, ret, ...rest) => { + const value = rest.length ? rest[0] : ret; if ($$.ctx && not_equal($$.ctx[i], $$.ctx[i] = value)) { if ($$.bound[i]) $$.bound[i](value); if (ready) make_dirty(component, i); diff --git a/src/runtime/internal/dev.ts b/src/runtime/internal/dev.ts index cb60131166..7f4b52eb3b 100644 --- a/src/runtime/internal/dev.ts +++ b/src/runtime/internal/dev.ts @@ -2,7 +2,7 @@ import { custom_event, append, insert, detach, listen, attr } from './dom'; import { SvelteComponent } from './Component'; export function dispatch_dev(type: string, detail?: T) { - document.dispatchEvent(custom_event(type, detail)); + document.dispatchEvent(custom_event(type, { version: '__VERSION__', ...detail })); } export function append_dev(target: Node, node: Node) { @@ -79,6 +79,16 @@ export function set_data_dev(text, data) { text.data = data; } +export function validate_each_argument(arg) { + if (!arg || !('length' in arg)) { + let msg = '{#each} only iterates over array-like objects.'; + if (typeof Symbol === 'function' && arg && Symbol.iterator in arg) { + msg += ' You can use a spread to convert this iterable into an array.'; + } + throw new Error(msg); + } +} + type Props = Record; export interface SvelteComponentDev { diff --git a/src/runtime/internal/dom.ts b/src/runtime/internal/dom.ts index c641315bc3..9ab9a4395f 100644 --- a/src/runtime/internal/dom.ts +++ b/src/runtime/internal/dom.ts @@ -98,7 +98,7 @@ export function set_attributes(node: Element & ElementCSSInlineStyle, attributes node.removeAttribute(key); } else if (key === 'style') { node.style.cssText = attributes[key]; - } else if (descriptors[key] && descriptors[key].set) { + } else if (key === '__value' || descriptors[key] && descriptors[key].set) { node[key] = attributes[key]; } else { attr(node, key, attributes[key]); @@ -152,11 +152,16 @@ export function claim_element(nodes, name, attributes, svg) { for (let i = 0; i < nodes.length; i += 1) { const node = nodes[i]; if (node.nodeName === name) { - for (let j = 0; j < node.attributes.length; j += 1) { + let j = 0; + while (j < node.attributes.length) { const attribute = node.attributes[j]; - if (!attributes[attribute.name]) node.removeAttribute(attribute.name); + if (attributes[attribute.name]) { + j++; + } else { + node.removeAttribute(attribute.name); + } } - return nodes.splice(i, 1)[0]; // TODO strip unwanted attributes + return nodes.splice(i, 1)[0]; } } @@ -273,6 +278,10 @@ export function custom_event(type: string, detail?: T) { return e; } +export function query_selector_all(selector: string, parent: HTMLElement = document.body) { + return Array.from(parent.querySelectorAll(selector)); +} + export class HtmlTag { e: HTMLElement; n: ChildNode[]; diff --git a/src/runtime/internal/keyed_each.ts b/src/runtime/internal/keyed_each.ts index 8382a2f76a..b397335c87 100644 --- a/src/runtime/internal/keyed_each.ts +++ b/src/runtime/internal/keyed_each.ts @@ -108,9 +108,13 @@ export function update_keyed_each(old_blocks, dirty, get_key, dynamic, ctx, list return new_blocks; } -export function measure(blocks) { - const rects = {}; - let i = blocks.length; - while (i--) rects[blocks[i].key] = blocks[i].node.getBoundingClientRect(); - return rects; +export function validate_each_keys(ctx, list, get_context, get_key) { + const keys = new Set(); + for (let i = 0; i < list.length; i++) { + const key = get_key(get_context(ctx, list, i)); + if (keys.has(key)) { + throw new Error(`Cannot have duplicate keys in a keyed each`); + } + keys.add(key); + } } diff --git a/src/runtime/internal/scheduler.ts b/src/runtime/internal/scheduler.ts index 04c1264ab1..b0db71035a 100644 --- a/src/runtime/internal/scheduler.ts +++ b/src/runtime/internal/scheduler.ts @@ -31,18 +31,23 @@ export function add_flush_callback(fn) { flush_callbacks.push(fn); } +let flushing = false; +const seen_callbacks = new Set(); export function flush() { - const seen_callbacks = new Set(); + if (flushing) return; + flushing = true; do { // first, call beforeUpdate functions // and update components - while (dirty_components.length) { - const component = dirty_components.shift(); + for (let i = 0; i < dirty_components.length; i += 1) { + const component = dirty_components[i]; set_current_component(component); update(component.$$); } + dirty_components.length = 0; + while (binding_callbacks.length) binding_callbacks.pop()(); // then, once components are updated, call @@ -52,10 +57,10 @@ export function flush() { const callback = render_callbacks[i]; if (!seen_callbacks.has(callback)) { - callback(); - // ...so guard against infinite loops seen_callbacks.add(callback); + + callback(); } } @@ -67,6 +72,8 @@ export function flush() { } update_scheduled = false; + flushing = false; + seen_callbacks.clear(); } function update($$) { diff --git a/src/runtime/internal/ssr.ts b/src/runtime/internal/ssr.ts index 274006f243..646a81d817 100644 --- a/src/runtime/internal/ssr.ts +++ b/src/runtime/internal/ssr.ts @@ -25,9 +25,7 @@ export function spread(args, classes_to_add) { else if (boolean_attributes.has(name.toLowerCase())) { if (value) str += " " + name; } else if (value != null) { - str += " " + name + "=" + JSON.stringify(String(value) - .replace(/"/g, '"') - .replace(/'/g, ''')); + str += ` ${name}="${String(value).replace(/"/g, '"').replace(/'/g, ''')}"`; } }); @@ -103,12 +101,13 @@ export function create_ssr_component(fn) { on_destroy = []; const result: { + title: string; head: string; css: Set<{ map: null; code: string; }>; - } = { head: '', css: new Set() }; + } = { title: '', head: '', css: new Set() }; const html = $$render(result, props, {}, options); @@ -120,7 +119,7 @@ export function create_ssr_component(fn) { code: Array.from(result.css).map(css => css.code).join('\n'), map: null // TODO }, - head: result.head + head: result.title + result.head }; }, diff --git a/src/runtime/internal/utils.ts b/src/runtime/internal/utils.ts index c94a135869..4ba7e18c15 100644 --- a/src/runtime/internal/utils.ts +++ b/src/runtime/internal/utils.ts @@ -43,13 +43,16 @@ export function not_equal(a, b) { } export function validate_store(store, name) { - if (!store || typeof store.subscribe !== 'function') { + if (store != null && typeof store.subscribe !== 'function') { throw new Error(`'${name}' is not a store with a 'subscribe' method`); } } -export function subscribe(store, callback) { - const unsub = store.subscribe(callback); +export function subscribe(store, ...callbacks) { + if (store == null) { + return noop; + } + const unsub = store.subscribe(...callbacks); return unsub.unsubscribe ? () => unsub.unsubscribe() : unsub; } diff --git a/src/runtime/store/index.ts b/src/runtime/store/index.ts index 64a63d4179..2bbfdfcd60 100644 --- a/src/runtime/store/index.ts +++ b/src/runtime/store/index.ts @@ -1,4 +1,4 @@ -import { run_all, noop, safe_not_equal, is_function, get_store_value } from 'svelte/internal'; +import { run_all, subscribe, noop, safe_not_equal, is_function, get_store_value } from 'svelte/internal'; /** Callback to inform of a value updates. */ type Subscriber = (value: T) => void; @@ -173,7 +173,8 @@ export function derived(stores: Stores, fn: Function, initial_value?: T): Rea } }; - const unsubscribers = stores_array.map((store, i) => store.subscribe( + const unsubscribers = stores_array.map((store, i) => subscribe( + store, (value) => { values[i] = value; pending &= ~(1 << i); diff --git a/test/css/samples/attribute-selector-word-arbitrary-whitespace/expected.css b/test/css/samples/attribute-selector-word-arbitrary-whitespace/expected.css new file mode 100644 index 0000000000..c1c0f3fdf6 --- /dev/null +++ b/test/css/samples/attribute-selector-word-arbitrary-whitespace/expected.css @@ -0,0 +1 @@ +.foo.svelte-xyz{color:red}[class~="bar"].svelte-xyz{background:blue} \ No newline at end of file diff --git a/test/css/samples/attribute-selector-word-arbitrary-whitespace/input.svelte b/test/css/samples/attribute-selector-word-arbitrary-whitespace/input.svelte new file mode 100644 index 0000000000..758aa27e0b --- /dev/null +++ b/test/css/samples/attribute-selector-word-arbitrary-whitespace/input.svelte @@ -0,0 +1,13 @@ +
+ + diff --git a/test/css/samples/global-with-unused-descendant/_config.js b/test/css/samples/global-with-unused-descendant/_config.js new file mode 100644 index 0000000000..3ed2dd2728 --- /dev/null +++ b/test/css/samples/global-with-unused-descendant/_config.js @@ -0,0 +1,24 @@ +export default { + warnings: [{ + code: 'css-unused-selector', + end: { + character: 27, + column: 19, + line: 2 + }, + frame: ` + 1: diff --git a/test/helpers.js b/test/helpers.js index 2a03e0f436..a764d43f96 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -45,6 +45,12 @@ export function tryToReadFile(file) { } } +export function cleanRequireCache() { + Object.keys(require.cache) + .filter(x => x.endsWith('.svelte')) + .forEach(file => delete require.cache[file]); +} + const virtualConsole = new jsdom.VirtualConsole(); virtualConsole.sendTo(console); @@ -68,6 +74,7 @@ window.scrollTo = function(pageXOffset, pageYOffset) { export function env() { window.document.title = ''; + window.document.head.innerHTML = ''; window.document.body.innerHTML = '
'; return window; diff --git a/test/hydration/index.js b/test/hydration/index.js index a0bfd6de4b..f57a0cdc1a 100644 --- a/test/hydration/index.js +++ b/test/hydration/index.js @@ -67,8 +67,16 @@ describe('hydration', () => { } const target = window.document.body; + const head = window.document.head; + target.innerHTML = fs.readFileSync(`${cwd}/_before.html`, 'utf-8'); + let before_head; + try { + before_head = fs.readFileSync(`${cwd}/_before_head.html`, 'utf-8'); + head.innerHTML = before_head; + } catch (err) {} + const snapshot = config.snapshot ? config.snapshot(target) : {}; const component = new SvelteComponent({ @@ -88,6 +96,19 @@ describe('hydration', () => { } } + if (before_head) { + try { + assert.htmlEqual(head.innerHTML, fs.readFileSync(`${cwd}/_after_head.html`, 'utf-8')); + } catch (error) { + if (shouldUpdateExpected()) { + fs.writeFileSync(`${cwd}/_after_head.html`, head.innerHTML); + console.log(`Updated ${cwd}/_after_head.html.`); + } else { + throw error; + } + } + } + if (config.test) { config.test(assert, target, snapshot, component, window); } else { diff --git a/test/hydration/samples/each-else/_after.html b/test/hydration/samples/each-else/_after.html new file mode 100644 index 0000000000..7920500ec3 --- /dev/null +++ b/test/hydration/samples/each-else/_after.html @@ -0,0 +1,4 @@ +

Hello, world

+

+ weird +

\ No newline at end of file diff --git a/test/hydration/samples/each-else/_before.html b/test/hydration/samples/each-else/_before.html new file mode 100644 index 0000000000..7920500ec3 --- /dev/null +++ b/test/hydration/samples/each-else/_before.html @@ -0,0 +1,4 @@ +

Hello, world

+

+ weird +

\ No newline at end of file diff --git a/test/hydration/samples/each-else/main.svelte b/test/hydration/samples/each-else/main.svelte new file mode 100644 index 0000000000..64d3a37c58 --- /dev/null +++ b/test/hydration/samples/each-else/main.svelte @@ -0,0 +1,15 @@ + + +

Hello, {name}

+{#each array as elem} +

+ item +

+{:else} +

+ weird +

+{/each} diff --git a/test/hydration/samples/element-attribute-removed/_before.html b/test/hydration/samples/element-attribute-removed/_before.html index c6a8a8c95d..80c0591a4d 100644 --- a/test/hydration/samples/element-attribute-removed/_before.html +++ b/test/hydration/samples/element-attribute-removed/_before.html @@ -1 +1 @@ -
\ No newline at end of file +
\ No newline at end of file diff --git a/test/hydration/samples/head-meta-hydrate-duplicate/_after.html b/test/hydration/samples/head-meta-hydrate-duplicate/_after.html new file mode 100644 index 0000000000..3e5b375f0a --- /dev/null +++ b/test/hydration/samples/head-meta-hydrate-duplicate/_after.html @@ -0,0 +1 @@ +
Just a dummy page.
\ No newline at end of file diff --git a/test/hydration/samples/head-meta-hydrate-duplicate/_after_head.html b/test/hydration/samples/head-meta-hydrate-duplicate/_after_head.html new file mode 100644 index 0000000000..10cf2c8b9a --- /dev/null +++ b/test/hydration/samples/head-meta-hydrate-duplicate/_after_head.html @@ -0,0 +1,4 @@ +Some Title + + + \ No newline at end of file diff --git a/test/hydration/samples/head-meta-hydrate-duplicate/_before.html b/test/hydration/samples/head-meta-hydrate-duplicate/_before.html new file mode 100644 index 0000000000..3e5b375f0a --- /dev/null +++ b/test/hydration/samples/head-meta-hydrate-duplicate/_before.html @@ -0,0 +1 @@ +
Just a dummy page.
\ No newline at end of file diff --git a/test/hydration/samples/head-meta-hydrate-duplicate/_before_head.html b/test/hydration/samples/head-meta-hydrate-duplicate/_before_head.html new file mode 100644 index 0000000000..107753cdd0 --- /dev/null +++ b/test/hydration/samples/head-meta-hydrate-duplicate/_before_head.html @@ -0,0 +1,4 @@ +Some Title + + + \ No newline at end of file diff --git a/test/hydration/samples/head-meta-hydrate-duplicate/_config.js b/test/hydration/samples/head-meta-hydrate-duplicate/_config.js new file mode 100644 index 0000000000..482efd564d --- /dev/null +++ b/test/hydration/samples/head-meta-hydrate-duplicate/_config.js @@ -0,0 +1,5 @@ +export default { + test(assert, target, snapshot, component, window) { + assert.equal(window.document.querySelectorAll('meta').length, 2); + } +}; diff --git a/test/hydration/samples/head-meta-hydrate-duplicate/main.svelte b/test/hydration/samples/head-meta-hydrate-duplicate/main.svelte new file mode 100644 index 0000000000..1a8b125dd2 --- /dev/null +++ b/test/hydration/samples/head-meta-hydrate-duplicate/main.svelte @@ -0,0 +1,8 @@ + + Some Title + + + + + +
Just a dummy page.
\ No newline at end of file diff --git a/test/js/samples/action-custom-event-handler/expected.js b/test/js/samples/action-custom-event-handler/expected.js index da42603895..ead6d90e06 100644 --- a/test/js/samples/action-custom-event-handler/expected.js +++ b/test/js/samples/action-custom-event-handler/expected.js @@ -20,10 +20,10 @@ function create_fragment(ctx) { c() { button = element("button"); button.textContent = "foo"; - dispose = action_destroyer(foo_action = foo.call(null, button, /*foo_function*/ ctx[1])); }, m(target, anchor) { insert(target, button, anchor); + dispose = action_destroyer(foo_action = foo.call(null, button, /*foo_function*/ ctx[1])); }, p(ctx, [dirty]) { if (foo_action && is_function(foo_action.update) && dirty & /*bar*/ 1) foo_action.update.call(null, /*foo_function*/ ctx[1]); @@ -42,8 +42,8 @@ function handleFoo(bar) { } function foo(node, callback) { - -} + +} // code goes here function instance($$self, $$props, $$invalidate) { let { bar } = $$props; diff --git a/test/js/samples/action/expected.js b/test/js/samples/action/expected.js index dc3ebb5cf8..22d9cd939c 100644 --- a/test/js/samples/action/expected.js +++ b/test/js/samples/action/expected.js @@ -21,10 +21,10 @@ function create_fragment(ctx) { a = element("a"); a.textContent = "Test"; attr(a, "href", "#"); - dispose = action_destroyer(link_action = link.call(null, a)); }, m(target, anchor) { insert(target, a, anchor); + dispose = action_destroyer(link_action = link.call(null, a)); }, p: noop, i: noop, diff --git a/test/js/samples/bind-online/expected.js b/test/js/samples/bind-online/expected.js index 8285646481..e129e66d71 100644 --- a/test/js/samples/bind-online/expected.js +++ b/test/js/samples/bind-online/expected.js @@ -14,13 +14,13 @@ function create_fragment(ctx) { add_render_callback(/*onlinestatuschanged*/ ctx[1]); return { - c() { + c: noop, + m(target, anchor) { dispose = [ listen(window, "online", /*onlinestatuschanged*/ ctx[1]), listen(window, "offline", /*onlinestatuschanged*/ ctx[1]) ]; }, - m: noop, p: noop, i: noop, o: noop, diff --git a/test/js/samples/bind-open/expected.js b/test/js/samples/bind-open/expected.js index d4f148cac9..7d66145f0a 100644 --- a/test/js/samples/bind-open/expected.js +++ b/test/js/samples/bind-open/expected.js @@ -20,12 +20,11 @@ function create_fragment(ctx) { details.innerHTML = `summarycontent `; - - dispose = listen(details, "toggle", /*details_toggle_handler*/ ctx[1]); }, m(target, anchor) { insert(target, details, anchor); details.open = /*open*/ ctx[0]; + dispose = listen(details, "toggle", /*details_toggle_handler*/ ctx[1]); }, p(ctx, [dirty]) { if (dirty & /*open*/ 1) { diff --git a/test/js/samples/bindings-readonly-order/expected.js b/test/js/samples/bindings-readonly-order/expected.js index cf30686662..db0e7cb007 100644 --- a/test/js/samples/bindings-readonly-order/expected.js +++ b/test/js/samples/bindings-readonly-order/expected.js @@ -26,16 +26,16 @@ function create_fragment(ctx) { input1 = element("input"); attr(input0, "type", "file"); attr(input1, "type", "file"); - - dispose = [ - listen(input0, "change", /*input0_change_handler*/ ctx[1]), - listen(input1, "change", /*input1_change_handler*/ ctx[2]) - ]; }, m(target, anchor) { insert(target, input0, anchor); insert(target, t, anchor); insert(target, input1, anchor); + + dispose = [ + listen(input0, "change", /*input0_change_handler*/ ctx[1]), + listen(input1, "change", /*input1_change_handler*/ ctx[2]) + ]; }, p: noop, i: noop, diff --git a/test/js/samples/capture-inject-dev-only/expected.js b/test/js/samples/capture-inject-dev-only/expected.js index 6c639d9207..a314b0cff3 100644 --- a/test/js/samples/capture-inject-dev-only/expected.js +++ b/test/js/samples/capture-inject-dev-only/expected.js @@ -28,7 +28,6 @@ function create_fragment(ctx) { t0 = text(/*foo*/ ctx[0]); t1 = space(); input = element("input"); - dispose = listen(input, "input", /*input_input_handler*/ ctx[1]); }, m(target, anchor) { insert(target, p, anchor); @@ -36,6 +35,7 @@ function create_fragment(ctx) { insert(target, t1, anchor); insert(target, input, anchor); set_input_value(input, /*foo*/ ctx[0]); + dispose = listen(input, "input", /*input_input_handler*/ ctx[1]); }, p(ctx, [dirty]) { if (dirty & /*foo*/ 1) set_data(t0, /*foo*/ ctx[0]); diff --git a/test/js/samples/collapses-text-around-comments/expected.js b/test/js/samples/collapses-text-around-comments/expected.js index 8c0e9d5373..6fef0f9490 100644 --- a/test/js/samples/collapses-text-around-comments/expected.js +++ b/test/js/samples/collapses-text-around-comments/expected.js @@ -63,4 +63,4 @@ class Component extends SvelteComponent { } } -export default Component; +export default Component; \ No newline at end of file diff --git a/test/js/samples/component-static-var/expected.js b/test/js/samples/component-static-var/expected.js index e01402b6d4..a65d9186a7 100644 --- a/test/js/samples/component-static-var/expected.js +++ b/test/js/samples/component-static-var/expected.js @@ -35,7 +35,6 @@ function create_fragment(ctx) { create_component(bar.$$.fragment); t1 = space(); input = element("input"); - dispose = listen(input, "input", /*input_input_handler*/ ctx[1]); }, m(target, anchor) { mount_component(foo, target, anchor); @@ -45,6 +44,7 @@ function create_fragment(ctx) { insert(target, input, anchor); set_input_value(input, /*z*/ ctx[0]); current = true; + dispose = listen(input, "input", /*input_input_handler*/ ctx[1]); }, p(ctx, [dirty]) { const bar_changes = {}; diff --git a/test/js/samples/component-store-reassign-invalidate/expected.js b/test/js/samples/component-store-reassign-invalidate/expected.js index 02a74cf22e..771b20dec4 100644 --- a/test/js/samples/component-store-reassign-invalidate/expected.js +++ b/test/js/samples/component-store-reassign-invalidate/expected.js @@ -31,13 +31,13 @@ function create_fragment(ctx) { t1 = space(); button = element("button"); button.textContent = "reset"; - dispose = listen(button, "click", /*click_handler*/ ctx[2]); }, m(target, anchor) { insert(target, h1, anchor); append(h1, t0); insert(target, t1, anchor); insert(target, button, anchor); + dispose = listen(button, "click", /*click_handler*/ ctx[2]); }, p(ctx, [dirty]) { if (dirty & /*$foo*/ 2) set_data(t0, /*$foo*/ ctx[1]); diff --git a/test/js/samples/css-media-query/expected.js b/test/js/samples/css-media-query/expected.js index 0566c22ddd..f477670059 100644 --- a/test/js/samples/css-media-query/expected.js +++ b/test/js/samples/css-media-query/expected.js @@ -46,4 +46,4 @@ class Component extends SvelteComponent { } } -export default Component; +export default Component; \ No newline at end of file diff --git a/test/js/samples/debug-empty/expected.js b/test/js/samples/debug-empty/expected.js index 059588c333..87d78bd698 100644 --- a/test/js/samples/debug-empty/expected.js +++ b/test/js/samples/debug-empty/expected.js @@ -105,7 +105,7 @@ class Component extends SvelteComponentDev { }); const { ctx } = this.$$; - const props = options.props || ({}); + const props = options.props || {}; if (/*name*/ ctx[0] === undefined && !("name" in props)) { console.warn(" was created without expected prop 'name'"); diff --git a/test/js/samples/debug-foo-bar-baz-things/expected.js b/test/js/samples/debug-foo-bar-baz-things/expected.js index ea16a38cdd..589c4a7832 100644 --- a/test/js/samples/debug-foo-bar-baz-things/expected.js +++ b/test/js/samples/debug-foo-bar-baz-things/expected.js @@ -13,7 +13,8 @@ import { safe_not_equal, set_data_dev, space, - text + text, + validate_each_argument } from "svelte/internal"; const file = undefined; @@ -88,6 +89,7 @@ function create_fragment(ctx) { let t1; let t2; let each_value = /*things*/ ctx[0]; + validate_each_argument(each_value); let each_blocks = []; for (let i = 0; i < each_value.length; i += 1) { @@ -122,6 +124,7 @@ function create_fragment(ctx) { p: function update(ctx, [dirty]) { if (dirty & /*things*/ 1) { each_value = /*things*/ ctx[0]; + validate_each_argument(each_value); let i; for (i = 0; i < each_value.length; i += 1) { @@ -212,7 +215,7 @@ class Component extends SvelteComponentDev { }); const { ctx } = this.$$; - const props = options.props || ({}); + const props = options.props || {}; if (/*things*/ ctx[0] === undefined && !("things" in props)) { console.warn(" was created without expected prop 'things'"); diff --git a/test/js/samples/debug-foo/expected.js b/test/js/samples/debug-foo/expected.js index 8e3d7a005c..10129e1b28 100644 --- a/test/js/samples/debug-foo/expected.js +++ b/test/js/samples/debug-foo/expected.js @@ -13,7 +13,8 @@ import { safe_not_equal, set_data_dev, space, - text + text, + validate_each_argument } from "svelte/internal"; const file = undefined; @@ -82,6 +83,7 @@ function create_fragment(ctx) { let t1; let t2; let each_value = /*things*/ ctx[0]; + validate_each_argument(each_value); let each_blocks = []; for (let i = 0; i < each_value.length; i += 1) { @@ -116,6 +118,7 @@ function create_fragment(ctx) { p: function update(ctx, [dirty]) { if (dirty & /*things*/ 1) { each_value = /*things*/ ctx[0]; + validate_each_argument(each_value); let i; for (i = 0; i < each_value.length; i += 1) { @@ -200,7 +203,7 @@ class Component extends SvelteComponentDev { }); const { ctx } = this.$$; - const props = options.props || ({}); + const props = options.props || {}; if (/*things*/ ctx[0] === undefined && !("things" in props)) { console.warn(" was created without expected prop 'things'"); diff --git a/test/js/samples/debug-no-dependencies/expected.js b/test/js/samples/debug-no-dependencies/expected.js index 2238e169aa..9a5cfa3226 100644 --- a/test/js/samples/debug-no-dependencies/expected.js +++ b/test/js/samples/debug-no-dependencies/expected.js @@ -10,7 +10,8 @@ import { noop, safe_not_equal, space, - text + text, + validate_each_argument } from "svelte/internal"; const file = undefined; @@ -64,6 +65,7 @@ function create_each_block(ctx) { function create_fragment(ctx) { let each_1_anchor; let each_value = things; + validate_each_argument(each_value); let each_blocks = []; for (let i = 0; i < each_value.length; i += 1) { @@ -91,6 +93,7 @@ function create_fragment(ctx) { p: function update(ctx, [dirty]) { if (dirty & /*things*/ 0) { each_value = things; + validate_each_argument(each_value); let i; for (i = 0; i < each_value.length; i += 1) { diff --git a/test/js/samples/dev-warning-missing-data-computed/expected.js b/test/js/samples/dev-warning-missing-data-computed/expected.js index 286884ca16..9c28e04064 100644 --- a/test/js/samples/dev-warning-missing-data-computed/expected.js +++ b/test/js/samples/dev-warning-missing-data-computed/expected.js @@ -109,7 +109,7 @@ class Component extends SvelteComponentDev { }); const { ctx } = this.$$; - const props = options.props || ({}); + const props = options.props || {}; if (/*foo*/ ctx[0] === undefined && !("foo" in props)) { console.warn(" was created without expected prop 'foo'"); diff --git a/test/js/samples/dont-invalidate-this/expected.js b/test/js/samples/dont-invalidate-this/expected.js index 98f638dfcf..f5f6d07812 100644 --- a/test/js/samples/dont-invalidate-this/expected.js +++ b/test/js/samples/dont-invalidate-this/expected.js @@ -17,10 +17,10 @@ function create_fragment(ctx) { return { c() { input = element("input"); - dispose = listen(input, "input", make_uppercase); }, m(target, anchor) { insert(target, input, anchor); + dispose = listen(input, "input", make_uppercase); }, p: noop, i: noop, diff --git a/test/js/samples/each-block-keyed-animated/expected.js b/test/js/samples/each-block-keyed-animated/expected.js index 3b697d6860..7fb81c27a2 100644 --- a/test/js/samples/each-block-keyed-animated/expected.js +++ b/test/js/samples/each-block-keyed-animated/expected.js @@ -92,10 +92,12 @@ function create_fragment(ctx) { insert(target, each_1_anchor, anchor); }, p(ctx, [dirty]) { - const each_value = /*things*/ ctx[0]; - for (let i = 0; i < each_blocks.length; i += 1) each_blocks[i].r(); - each_blocks = update_keyed_each(each_blocks, dirty, get_key, 1, ctx, each_value, each_1_lookup, each_1_anchor.parentNode, fix_and_destroy_block, create_each_block, each_1_anchor, get_each_context); - for (let i = 0; i < each_blocks.length; i += 1) each_blocks[i].a(); + if (dirty & /*things*/ 1) { + const each_value = /*things*/ ctx[0]; + for (let i = 0; i < each_blocks.length; i += 1) each_blocks[i].r(); + each_blocks = update_keyed_each(each_blocks, dirty, get_key, 1, ctx, each_value, each_1_lookup, each_1_anchor.parentNode, fix_and_destroy_block, create_each_block, each_1_anchor, get_each_context); + for (let i = 0; i < each_blocks.length; i += 1) each_blocks[i].a(); + } }, i: noop, o: noop, diff --git a/test/js/samples/each-block-keyed/expected.js b/test/js/samples/each-block-keyed/expected.js index 1cd9a31ece..ad8c074e99 100644 --- a/test/js/samples/each-block-keyed/expected.js +++ b/test/js/samples/each-block-keyed/expected.js @@ -77,8 +77,10 @@ function create_fragment(ctx) { insert(target, each_1_anchor, anchor); }, p(ctx, [dirty]) { - const each_value = /*things*/ ctx[0]; - each_blocks = update_keyed_each(each_blocks, dirty, get_key, 1, ctx, each_value, each_1_lookup, each_1_anchor.parentNode, destroy_block, create_each_block, each_1_anchor, get_each_context); + if (dirty & /*things*/ 1) { + const each_value = /*things*/ ctx[0]; + each_blocks = update_keyed_each(each_blocks, dirty, get_key, 1, ctx, each_value, each_1_lookup, each_1_anchor.parentNode, destroy_block, create_each_block, each_1_anchor, get_each_context); + } }, i: noop, o: noop, diff --git a/test/js/samples/event-handler-dynamic/expected.js b/test/js/samples/event-handler-dynamic/expected.js index 42c6b2951a..16b4a3f626 100644 --- a/test/js/samples/event-handler-dynamic/expected.js +++ b/test/js/samples/event-handler-dynamic/expected.js @@ -42,14 +42,6 @@ function create_fragment(ctx) { t5 = space(); button2 = element("button"); button2.textContent = "click"; - - dispose = [ - listen(button0, "click", /*updateHandler1*/ ctx[2]), - listen(button1, "click", /*updateHandler2*/ ctx[3]), - listen(button2, "click", function () { - if (is_function(/*clickHandler*/ ctx[0])) /*clickHandler*/ ctx[0].apply(this, arguments); - }) - ]; }, m(target, anchor) { insert(target, p0, anchor); @@ -61,6 +53,14 @@ function create_fragment(ctx) { append(p1, t4); insert(target, t5, anchor); insert(target, button2, anchor); + + dispose = [ + listen(button0, "click", /*updateHandler1*/ ctx[2]), + listen(button1, "click", /*updateHandler2*/ ctx[3]), + listen(button2, "click", function () { + if (is_function(/*clickHandler*/ ctx[0])) /*clickHandler*/ ctx[0].apply(this, arguments); + }) + ]; }, p(new_ctx, [dirty]) { ctx = new_ctx; diff --git a/test/js/samples/event-handler-no-passive/expected.js b/test/js/samples/event-handler-no-passive/expected.js index 6f04e67808..c519fac668 100644 --- a/test/js/samples/event-handler-no-passive/expected.js +++ b/test/js/samples/event-handler-no-passive/expected.js @@ -20,10 +20,10 @@ function create_fragment(ctx) { a = element("a"); a.textContent = "this should not navigate to example.com"; attr(a, "href", "https://example.com"); - dispose = listen(a, "touchstart", touchstart_handler); }, m(target, anchor) { insert(target, a, anchor); + dispose = listen(a, "touchstart", touchstart_handler); }, p: noop, i: noop, diff --git a/test/js/samples/event-modifiers/expected.js b/test/js/samples/event-modifiers/expected.js index 3f324bb76d..c12c3523a0 100644 --- a/test/js/samples/event-modifiers/expected.js +++ b/test/js/samples/event-modifiers/expected.js @@ -35,13 +35,6 @@ function create_fragment(ctx) { t3 = space(); button2 = element("button"); button2.textContent = "or me!"; - - dispose = [ - listen(button0, "click", stop_propagation(prevent_default(handleClick))), - listen(button1, "click", handleClick, { once: true, capture: true }), - listen(button2, "click", handleClick, true), - listen(div, "touchstart", handleTouchstart, { passive: true }) - ]; }, m(target, anchor) { insert(target, div, anchor); @@ -50,6 +43,13 @@ function create_fragment(ctx) { append(div, button1); append(div, t3); append(div, button2); + + dispose = [ + listen(button0, "click", stop_propagation(prevent_default(handleClick))), + listen(button1, "click", handleClick, { once: true, capture: true }), + listen(button2, "click", handleClick, true), + listen(div, "touchstart", handleTouchstart, { passive: true }) + ]; }, p: noop, i: noop, @@ -63,11 +63,11 @@ function create_fragment(ctx) { function handleTouchstart() { -} +} // ... function handleClick() { -} +} // ... class Component extends SvelteComponent { constructor(options) { diff --git a/test/js/samples/input-files/expected.js b/test/js/samples/input-files/expected.js index c3e46f0c79..2a2254fbd7 100644 --- a/test/js/samples/input-files/expected.js +++ b/test/js/samples/input-files/expected.js @@ -20,10 +20,10 @@ function create_fragment(ctx) { input = element("input"); attr(input, "type", "file"); input.multiple = true; - dispose = listen(input, "change", /*input_change_handler*/ ctx[1]); }, m(target, anchor) { insert(target, input, anchor); + dispose = listen(input, "change", /*input_change_handler*/ ctx[1]); }, p: noop, i: noop, diff --git a/test/js/samples/input-no-initial-value/expected.js b/test/js/samples/input-no-initial-value/expected.js index 8ff2b2798b..d588f0bf73 100644 --- a/test/js/samples/input-no-initial-value/expected.js +++ b/test/js/samples/input-no-initial-value/expected.js @@ -31,11 +31,6 @@ function create_fragment(ctx) { button.textContent = "Store"; attr(input, "type", "text"); input.required = true; - - dispose = [ - listen(input, "input", /*input_input_handler*/ ctx[2]), - listen(form, "submit", /*handleSubmit*/ ctx[1]) - ]; }, m(target, anchor) { insert(target, form, anchor); @@ -43,6 +38,11 @@ function create_fragment(ctx) { set_input_value(input, /*test*/ ctx[0]); append(form, t0); append(form, button); + + dispose = [ + listen(input, "input", /*input_input_handler*/ ctx[2]), + listen(form, "submit", /*handleSubmit*/ ctx[1]) + ]; }, p(ctx, [dirty]) { if (dirty & /*test*/ 1 && input.value !== /*test*/ ctx[0]) { diff --git a/test/js/samples/input-range/expected.js b/test/js/samples/input-range/expected.js index 5a074d9754..12dfd3e90e 100644 --- a/test/js/samples/input-range/expected.js +++ b/test/js/samples/input-range/expected.js @@ -22,16 +22,16 @@ function create_fragment(ctx) { c() { input = element("input"); attr(input, "type", "range"); + }, + m(target, anchor) { + insert(target, input, anchor); + set_input_value(input, /*value*/ ctx[0]); dispose = [ listen(input, "change", /*input_change_input_handler*/ ctx[1]), listen(input, "input", /*input_change_input_handler*/ ctx[1]) ]; }, - m(target, anchor) { - insert(target, input, anchor); - set_input_value(input, /*value*/ ctx[0]); - }, p(ctx, [dirty]) { if (dirty & /*value*/ 1) { set_input_value(input, /*value*/ ctx[0]); diff --git a/test/js/samples/input-value/expected.js b/test/js/samples/input-value/expected.js index 81753441e4..21c7bfc83b 100644 --- a/test/js/samples/input-value/expected.js +++ b/test/js/samples/input-value/expected.js @@ -30,7 +30,6 @@ function create_fragment(ctx) { t1 = text(/*name*/ ctx[0]); t2 = text("!"); input.value = /*name*/ ctx[0]; - dispose = listen(input, "input", /*onInput*/ ctx[1]); }, m(target, anchor) { insert(target, input, anchor); @@ -38,6 +37,7 @@ function create_fragment(ctx) { insert(target, h1, anchor); append(h1, t1); append(h1, t2); + dispose = listen(input, "input", /*onInput*/ ctx[1]); }, p(ctx, [dirty]) { if (dirty & /*name*/ 1 && input.value !== /*name*/ ctx[0]) { diff --git a/test/js/samples/input-without-blowback-guard/expected.js b/test/js/samples/input-without-blowback-guard/expected.js index 344976ade6..fefe867e14 100644 --- a/test/js/samples/input-without-blowback-guard/expected.js +++ b/test/js/samples/input-without-blowback-guard/expected.js @@ -19,11 +19,11 @@ function create_fragment(ctx) { c() { input = element("input"); attr(input, "type", "checkbox"); - dispose = listen(input, "change", /*input_change_handler*/ ctx[1]); }, m(target, anchor) { insert(target, input, anchor); input.checked = /*foo*/ ctx[0]; + dispose = listen(input, "change", /*input_change_handler*/ ctx[1]); }, p(ctx, [dirty]) { if (dirty & /*foo*/ 1) { diff --git a/test/js/samples/instrumentation-script-if-no-block/expected.js b/test/js/samples/instrumentation-script-if-no-block/expected.js index 4127a6d7d6..7634481a2d 100644 --- a/test/js/samples/instrumentation-script-if-no-block/expected.js +++ b/test/js/samples/instrumentation-script-if-no-block/expected.js @@ -30,7 +30,6 @@ function create_fragment(ctx) { p = element("p"); t2 = text("x: "); t3 = text(/*x*/ ctx[0]); - dispose = listen(button, "click", /*foo*/ ctx[1]); }, m(target, anchor) { insert(target, button, anchor); @@ -38,6 +37,7 @@ function create_fragment(ctx) { insert(target, p, anchor); append(p, t2); append(p, t3); + dispose = listen(button, "click", /*foo*/ ctx[1]); }, p(ctx, [dirty]) { if (dirty & /*x*/ 1) set_data(t3, /*x*/ ctx[0]); diff --git a/test/js/samples/instrumentation-script-x-equals-x/expected.js b/test/js/samples/instrumentation-script-x-equals-x/expected.js index 0d4493baf3..c154608cd5 100644 --- a/test/js/samples/instrumentation-script-x-equals-x/expected.js +++ b/test/js/samples/instrumentation-script-x-equals-x/expected.js @@ -31,7 +31,6 @@ function create_fragment(ctx) { p = element("p"); t2 = text("number of things: "); t3 = text(t3_value); - dispose = listen(button, "click", /*foo*/ ctx[1]); }, m(target, anchor) { insert(target, button, anchor); @@ -39,6 +38,7 @@ function create_fragment(ctx) { insert(target, p, anchor); append(p, t2); append(p, t3); + dispose = listen(button, "click", /*foo*/ ctx[1]); }, p(ctx, [dirty]) { if (dirty & /*things*/ 1 && t3_value !== (t3_value = /*things*/ ctx[0].length + "")) set_data(t3, t3_value); diff --git a/test/js/samples/instrumentation-template-if-no-block/expected.js b/test/js/samples/instrumentation-template-if-no-block/expected.js index 0bd627eb87..77780baa99 100644 --- a/test/js/samples/instrumentation-template-if-no-block/expected.js +++ b/test/js/samples/instrumentation-template-if-no-block/expected.js @@ -30,7 +30,6 @@ function create_fragment(ctx) { p = element("p"); t2 = text("x: "); t3 = text(/*x*/ ctx[0]); - dispose = listen(button, "click", /*click_handler*/ ctx[1]); }, m(target, anchor) { insert(target, button, anchor); @@ -38,6 +37,7 @@ function create_fragment(ctx) { insert(target, p, anchor); append(p, t2); append(p, t3); + dispose = listen(button, "click", /*click_handler*/ ctx[1]); }, p(ctx, [dirty]) { if (dirty & /*x*/ 1) set_data(t3, /*x*/ ctx[0]); diff --git a/test/js/samples/instrumentation-template-x-equals-x/expected.js b/test/js/samples/instrumentation-template-x-equals-x/expected.js index e049a6d39b..4fe45616c7 100644 --- a/test/js/samples/instrumentation-template-x-equals-x/expected.js +++ b/test/js/samples/instrumentation-template-x-equals-x/expected.js @@ -31,7 +31,6 @@ function create_fragment(ctx) { p = element("p"); t2 = text("number of things: "); t3 = text(t3_value); - dispose = listen(button, "click", /*click_handler*/ ctx[1]); }, m(target, anchor) { insert(target, button, anchor); @@ -39,6 +38,7 @@ function create_fragment(ctx) { insert(target, p, anchor); append(p, t2); append(p, t3); + dispose = listen(button, "click", /*click_handler*/ ctx[1]); }, p(ctx, [dirty]) { if (dirty & /*things*/ 1 && t3_value !== (t3_value = /*things*/ ctx[0].length + "")) set_data(t3, t3_value); diff --git a/test/js/samples/media-bindings/expected.js b/test/js/samples/media-bindings/expected.js index b5548a3efe..52fef36792 100644 --- a/test/js/samples/media-bindings/expected.js +++ b/test/js/samples/media-bindings/expected.js @@ -41,6 +41,17 @@ function create_fragment(ctx) { if (/*duration*/ ctx[4] === void 0) add_render_callback(() => /*audio_durationchange_handler*/ ctx[13].call(audio)); if (/*seeking*/ ctx[8] === void 0) add_render_callback(() => /*audio_seeking_seeked_handler*/ ctx[17].call(audio)); if (/*ended*/ ctx[9] === void 0) add_render_callback(() => /*audio_ended_handler*/ ctx[18].call(audio)); + }, + m(target, anchor) { + insert(target, audio, anchor); + + if (!isNaN(/*volume*/ ctx[6])) { + audio.volume = /*volume*/ ctx[6]; + } + + if (!isNaN(/*playbackRate*/ ctx[7])) { + audio.playbackRate = /*playbackRate*/ ctx[7]; + } dispose = [ listen(audio, "progress", /*audio_progress_handler*/ ctx[10]), @@ -56,17 +67,6 @@ function create_fragment(ctx) { listen(audio, "ended", /*audio_ended_handler*/ ctx[18]) ]; }, - m(target, anchor) { - insert(target, audio, anchor); - - if (!isNaN(/*volume*/ ctx[6])) { - audio.volume = /*volume*/ ctx[6]; - } - - if (!isNaN(/*playbackRate*/ ctx[7])) { - audio.playbackRate = /*playbackRate*/ ctx[7]; - } - }, p(ctx, [dirty]) { if (!audio_updating && dirty & /*currentTime*/ 8 && !isNaN(/*currentTime*/ ctx[3])) { audio.currentTime = /*currentTime*/ ctx[3]; diff --git a/test/js/samples/ssr-no-oncreate-etc/expected.js b/test/js/samples/ssr-no-oncreate-etc/expected.js index 276587eeca..803f06a882 100644 --- a/test/js/samples/ssr-no-oncreate-etc/expected.js +++ b/test/js/samples/ssr-no-oncreate-etc/expected.js @@ -13,7 +13,7 @@ function foo() { function swipe(node, callback) { -} +} // TODO implement const Component = create_ssr_component(($$result, $$props, $$bindings, $$slots) => { onMount(() => { diff --git a/test/js/samples/video-bindings/expected.js b/test/js/samples/video-bindings/expected.js index 5b734a70a6..c8cd1d84ce 100644 --- a/test/js/samples/video-bindings/expected.js +++ b/test/js/samples/video-bindings/expected.js @@ -37,16 +37,16 @@ function create_fragment(ctx) { video = element("video"); if (/*videoHeight*/ ctx[1] === void 0 || /*videoWidth*/ ctx[2] === void 0) add_render_callback(() => /*video_resize_handler*/ ctx[5].call(video)); add_render_callback(() => /*video_elementresize_handler*/ ctx[6].call(video)); + }, + m(target, anchor) { + insert(target, video, anchor); + video_resize_listener = add_resize_listener(video, /*video_elementresize_handler*/ ctx[6].bind(video)); dispose = [ listen(video, "timeupdate", video_timeupdate_handler), listen(video, "resize", /*video_resize_handler*/ ctx[5]) ]; }, - m(target, anchor) { - insert(target, video, anchor); - video_resize_listener = add_resize_listener(video, /*video_elementresize_handler*/ ctx[6].bind(video)); - }, p(ctx, [dirty]) { if (!video_updating && dirty & /*currentTime*/ 1 && !isNaN(/*currentTime*/ ctx[0])) { video.currentTime = /*currentTime*/ ctx[0]; diff --git a/test/js/samples/window-binding-online/expected.js b/test/js/samples/window-binding-online/expected.js index 8285646481..e129e66d71 100644 --- a/test/js/samples/window-binding-online/expected.js +++ b/test/js/samples/window-binding-online/expected.js @@ -14,13 +14,13 @@ function create_fragment(ctx) { add_render_callback(/*onlinestatuschanged*/ ctx[1]); return { - c() { + c: noop, + m(target, anchor) { dispose = [ listen(window, "online", /*onlinestatuschanged*/ ctx[1]), listen(window, "offline", /*onlinestatuschanged*/ ctx[1]) ]; }, - m: noop, p: noop, i: noop, o: noop, diff --git a/test/js/samples/window-binding-scroll/expected.js b/test/js/samples/window-binding-scroll/expected.js index f79212e25e..70c39eedd2 100644 --- a/test/js/samples/window-binding-scroll/expected.js +++ b/test/js/samples/window-binding-scroll/expected.js @@ -33,6 +33,11 @@ function create_fragment(ctx) { p = element("p"); t0 = text("scrolled to "); t1 = text(/*y*/ ctx[0]); + }, + m(target, anchor) { + insert(target, p, anchor); + append(p, t0); + append(p, t1); dispose = listen(window, "scroll", () => { scrolling = true; @@ -41,11 +46,6 @@ function create_fragment(ctx) { /*onwindowscroll*/ ctx[1](); }); }, - m(target, anchor) { - insert(target, p, anchor); - append(p, t0); - append(p, t1); - }, p(ctx, [dirty]) { if (dirty & /*y*/ 1 && !scrolling) { scrolling = true; diff --git a/test/parser/samples/action-with-identifier/output.json b/test/parser/samples/action-with-identifier/output.json index 435aee4444..9ae0011c52 100644 --- a/test/parser/samples/action-with-identifier/output.json +++ b/test/parser/samples/action-with-identifier/output.json @@ -20,6 +20,16 @@ "type": "Identifier", "start": 20, "end": 27, + "loc": { + "start": { + "line": 1, + "column": 20 + }, + "end": { + "line": 1, + "column": 27 + } + }, "name": "message" } } diff --git a/test/parser/samples/attribute-dynamic-boolean/output.json b/test/parser/samples/attribute-dynamic-boolean/output.json index 478144152a..ab6b3927ce 100644 --- a/test/parser/samples/attribute-dynamic-boolean/output.json +++ b/test/parser/samples/attribute-dynamic-boolean/output.json @@ -24,6 +24,16 @@ "type": "Identifier", "start": 20, "end": 28, + "loc": { + "start": { + "line": 1, + "column": 20 + }, + "end": { + "line": 1, + "column": 28 + } + }, "name": "readonly" } } diff --git a/test/parser/samples/attribute-dynamic-reserved/input.svelte b/test/parser/samples/attribute-dynamic-reserved/input.svelte deleted file mode 100644 index d973a9dea0..0000000000 --- a/test/parser/samples/attribute-dynamic-reserved/input.svelte +++ /dev/null @@ -1 +0,0 @@ -
\ No newline at end of file diff --git a/test/parser/samples/attribute-dynamic-reserved/output.json b/test/parser/samples/attribute-dynamic-reserved/output.json deleted file mode 100644 index 22be3c9087..0000000000 --- a/test/parser/samples/attribute-dynamic-reserved/output.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "html": { - "start": 0, - "end": 25, - "type": "Fragment", - "children": [ - { - "start": 0, - "end": 25, - "type": "Element", - "name": "div", - "attributes": [ - { - "start": 5, - "end": 18, - "type": "Attribute", - "name": "class", - "value": [ - { - "start": 11, - "end": 18, - "type": "MustacheTag", - "expression": { - "type": "Identifier", - "start": 12, - "end": 17, - "name": "class" - } - } - ] - } - ], - "children": [] - } - ] - } -} \ No newline at end of file diff --git a/test/parser/samples/attribute-dynamic/output.json b/test/parser/samples/attribute-dynamic/output.json index a48f376876..28b9da1dea 100644 --- a/test/parser/samples/attribute-dynamic/output.json +++ b/test/parser/samples/attribute-dynamic/output.json @@ -31,6 +31,16 @@ "type": "Identifier", "start": 20, "end": 25, + "loc": { + "start": { + "line": 1, + "column": 20 + }, + "end": { + "line": 1, + "column": 25 + } + }, "name": "color" } }, @@ -53,6 +63,16 @@ "type": "Identifier", "start": 30, "end": 35, + "loc": { + "start": { + "line": 1, + "column": 30 + }, + "end": { + "line": 1, + "column": 35 + } + }, "name": "color" } } diff --git a/test/parser/samples/attribute-unique-binding-error/error.json b/test/parser/samples/attribute-unique-binding-error/error.json new file mode 100644 index 0000000000..dd14572149 --- /dev/null +++ b/test/parser/samples/attribute-unique-binding-error/error.json @@ -0,0 +1,10 @@ +{ + "code": "duplicate-attribute", + "message": "Attributes need to be unique", + "start": { + "line": 1, + "column": 17, + "character": 17 + }, + "pos": 17 +} diff --git a/test/parser/samples/attribute-unique-binding-error/input.svelte b/test/parser/samples/attribute-unique-binding-error/input.svelte new file mode 100644 index 0000000000..78f854bad6 --- /dev/null +++ b/test/parser/samples/attribute-unique-binding-error/input.svelte @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/parser/samples/attribute-unique-shorthand-error/error.json b/test/parser/samples/attribute-unique-shorthand-error/error.json new file mode 100644 index 0000000000..dd14572149 --- /dev/null +++ b/test/parser/samples/attribute-unique-shorthand-error/error.json @@ -0,0 +1,10 @@ +{ + "code": "duplicate-attribute", + "message": "Attributes need to be unique", + "start": { + "line": 1, + "column": 17, + "character": 17 + }, + "pos": 17 +} diff --git a/test/parser/samples/attribute-unique-shorthand-error/input.svelte b/test/parser/samples/attribute-unique-shorthand-error/input.svelte new file mode 100644 index 0000000000..d486eab558 --- /dev/null +++ b/test/parser/samples/attribute-unique-shorthand-error/input.svelte @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/test/parser/samples/attribute-with-whitespace/output.json b/test/parser/samples/attribute-with-whitespace/output.json index 11d5aa5e06..edf05ca32e 100644 --- a/test/parser/samples/attribute-with-whitespace/output.json +++ b/test/parser/samples/attribute-with-whitespace/output.json @@ -20,6 +20,16 @@ "type": "Identifier", "start": 19, "end": 22, + "loc": { + "start": { + "line": 1, + "column": 19 + }, + "end": { + "line": 1, + "column": 22 + } + }, "name": "foo" } } diff --git a/test/parser/samples/binding/output.json b/test/parser/samples/binding/output.json index 558215f41a..c980b7f3eb 100644 --- a/test/parser/samples/binding/output.json +++ b/test/parser/samples/binding/output.json @@ -27,6 +27,16 @@ "type": "Identifier", "start": 50, "end": 54, + "loc": { + "start": { + "line": 5, + "column": 19 + }, + "end": { + "line": 5, + "column": 23 + } + }, "name": "name" } } diff --git a/test/parser/samples/each-block-destructured/output.json b/test/parser/samples/each-block-destructured/output.json index bafde1324d..425c609a2c 100644 --- a/test/parser/samples/each-block-destructured/output.json +++ b/test/parser/samples/each-block-destructured/output.json @@ -40,6 +40,16 @@ "type": "Identifier", "start": 46, "end": 49, + "loc": { + "start": { + "line": 2, + "column": 5 + }, + "end": { + "line": 2, + "column": 8 + } + }, "name": "key" } }, @@ -58,6 +68,16 @@ "type": "Identifier", "start": 53, "end": 58, + "loc": { + "start": { + "line": 2, + "column": 12 + }, + "end": { + "line": 2, + "column": 17 + } + }, "name": "value" } } diff --git a/test/parser/samples/each-block-else/output.json b/test/parser/samples/each-block-else/output.json index 19b5af19b4..2720ce5292 100644 --- a/test/parser/samples/each-block-else/output.json +++ b/test/parser/samples/each-block-else/output.json @@ -40,6 +40,16 @@ "type": "Identifier", "start": 31, "end": 37, + "loc": { + "start": { + "line": 2, + "column": 5 + }, + "end": { + "line": 2, + "column": 11 + } + }, "name": "animal" } } diff --git a/test/parser/samples/each-block-indexed/output.json b/test/parser/samples/each-block-indexed/output.json index 68a12ea31c..50f2000a36 100644 --- a/test/parser/samples/each-block-indexed/output.json +++ b/test/parser/samples/each-block-indexed/output.json @@ -40,6 +40,16 @@ "type": "Identifier", "start": 34, "end": 35, + "loc": { + "start": { + "line": 2, + "column": 5 + }, + "end": { + "line": 2, + "column": 6 + } + }, "name": "i" } }, @@ -58,6 +68,16 @@ "type": "Identifier", "start": 39, "end": 45, + "loc": { + "start": { + "line": 2, + "column": 10 + }, + "end": { + "line": 2, + "column": 16 + } + }, "name": "animal" } } diff --git a/test/parser/samples/each-block-keyed/output.json b/test/parser/samples/each-block-keyed/output.json index 61b5442c0d..7dc8681453 100644 --- a/test/parser/samples/each-block-keyed/output.json +++ b/test/parser/samples/each-block-keyed/output.json @@ -40,6 +40,16 @@ "type": "Identifier", "start": 37, "end": 41, + "loc": { + "start": { + "line": 2, + "column": 5 + }, + "end": { + "line": 2, + "column": 9 + } + }, "name": "todo" } } diff --git a/test/parser/samples/each-block/output.json b/test/parser/samples/each-block/output.json index 38bc7e1d04..6594fb50a6 100644 --- a/test/parser/samples/each-block/output.json +++ b/test/parser/samples/each-block/output.json @@ -40,6 +40,16 @@ "type": "Identifier", "start": 31, "end": 37, + "loc": { + "start": { + "line": 2, + "column": 5 + }, + "end": { + "line": 2, + "column": 11 + } + }, "name": "animal" } } diff --git a/test/parser/samples/element-with-mustache/output.json b/test/parser/samples/element-with-mustache/output.json index f7c2e9936b..049e373100 100644 --- a/test/parser/samples/element-with-mustache/output.json +++ b/test/parser/samples/element-with-mustache/output.json @@ -26,6 +26,16 @@ "type": "Identifier", "start": 11, "end": 15, + "loc": { + "start": { + "line": 1, + "column": 11 + }, + "end": { + "line": 1, + "column": 15 + } + }, "name": "name" } }, diff --git a/test/parser/samples/error-catch-before-closing/error.json b/test/parser/samples/error-catch-before-closing/error.json new file mode 100644 index 0000000000..30fbf5fbd5 --- /dev/null +++ b/test/parser/samples/error-catch-before-closing/error.json @@ -0,0 +1,6 @@ +{ + "code": "invalid-catch-placement", + "message": "Expected to close {#each} block before seeing {:catch} block", + "start": { "line": 3, "column": 7, "character": 41 }, + "pos": 41 +} diff --git a/test/parser/samples/error-catch-before-closing/input.svelte b/test/parser/samples/error-catch-before-closing/input.svelte new file mode 100644 index 0000000000..bd771701e2 --- /dev/null +++ b/test/parser/samples/error-catch-before-closing/input.svelte @@ -0,0 +1,4 @@ +{#await true} + {#each foo as bar} +{:catch f} +{/await} \ No newline at end of file diff --git a/test/parser/samples/error-else-before-closing-2/error.json b/test/parser/samples/error-else-before-closing-2/error.json new file mode 100644 index 0000000000..d35be1abf5 --- /dev/null +++ b/test/parser/samples/error-else-before-closing-2/error.json @@ -0,0 +1,6 @@ +{ + "code": "invalid-else-placement", + "message": "Expected to close {#await} block before seeing {:else} block", + "start": { "line": 3, "column": 6, "character": 29 }, + "pos": 29 +} diff --git a/test/parser/samples/error-else-before-closing-2/input.svelte b/test/parser/samples/error-else-before-closing-2/input.svelte new file mode 100644 index 0000000000..f865a9a533 --- /dev/null +++ b/test/parser/samples/error-else-before-closing-2/input.svelte @@ -0,0 +1,4 @@ +{#if true} + {#await p} +{:else} +{/if} \ No newline at end of file diff --git a/test/parser/samples/error-else-before-closing-3/error.json b/test/parser/samples/error-else-before-closing-3/error.json new file mode 100644 index 0000000000..e5ec210c75 --- /dev/null +++ b/test/parser/samples/error-else-before-closing-3/error.json @@ -0,0 +1,6 @@ +{ + "code": "invalid-else-placement", + "message": "Cannot have an {:else} block outside an {#if ...} or {#each ...} block", + "start": { "line": 2, "column": 6, "character": 11 }, + "pos": 11 +} diff --git a/test/parser/samples/error-else-before-closing-3/input.svelte b/test/parser/samples/error-else-before-closing-3/input.svelte new file mode 100644 index 0000000000..fb434f26a3 --- /dev/null +++ b/test/parser/samples/error-else-before-closing-3/input.svelte @@ -0,0 +1,2 @@ +
  • +{:else} \ No newline at end of file diff --git a/test/parser/samples/error-else-before-closing/error.json b/test/parser/samples/error-else-before-closing/error.json new file mode 100644 index 0000000000..5b6e490c0f --- /dev/null +++ b/test/parser/samples/error-else-before-closing/error.json @@ -0,0 +1,6 @@ +{ + "code": "invalid-else-placement", + "message": "Expected to close
  • tag before seeing {:else} block", + "start": { "line": 3, "column": 6, "character": 23 }, + "pos": 23 +} diff --git a/test/parser/samples/error-else-before-closing/input.svelte b/test/parser/samples/error-else-before-closing/input.svelte new file mode 100644 index 0000000000..a22863192d --- /dev/null +++ b/test/parser/samples/error-else-before-closing/input.svelte @@ -0,0 +1,4 @@ +{#if true} +
  • +{:else} +{/if} \ No newline at end of file diff --git a/test/parser/samples/error-else-if-before-closing-2/error.json b/test/parser/samples/error-else-if-before-closing-2/error.json new file mode 100644 index 0000000000..e55f4f11e5 --- /dev/null +++ b/test/parser/samples/error-else-if-before-closing-2/error.json @@ -0,0 +1,6 @@ +{ + "code": "invalid-elseif-placement", + "message": "Expected to close

    tag before seeing {:else if ...} block", + "start": { "line": 3, "column": 9, "character": 25 }, + "pos": 25 +} diff --git a/test/parser/samples/error-else-if-before-closing-2/input.svelte b/test/parser/samples/error-else-if-before-closing-2/input.svelte new file mode 100644 index 0000000000..5ae36df4eb --- /dev/null +++ b/test/parser/samples/error-else-if-before-closing-2/input.svelte @@ -0,0 +1,4 @@ +{#if true} +

    +{:else if false} +{/if} \ No newline at end of file diff --git a/test/parser/samples/error-else-if-before-closing/error.json b/test/parser/samples/error-else-if-before-closing/error.json new file mode 100644 index 0000000000..21d16c72a4 --- /dev/null +++ b/test/parser/samples/error-else-if-before-closing/error.json @@ -0,0 +1,6 @@ +{ + "code": "invalid-elseif-placement", + "message": "Expected to close {#await} block before seeing {:else if ...} block", + "start": { "line": 3, "column": 9, "character": 34 }, + "pos": 34 +} diff --git a/test/parser/samples/error-else-if-before-closing/input.svelte b/test/parser/samples/error-else-if-before-closing/input.svelte new file mode 100644 index 0000000000..486a921a9a --- /dev/null +++ b/test/parser/samples/error-else-if-before-closing/input.svelte @@ -0,0 +1,4 @@ +{#if true} + {#await foo} +{:else if false} +{/if} \ No newline at end of file diff --git a/test/parser/samples/error-else-if-without-if/error.json b/test/parser/samples/error-else-if-without-if/error.json new file mode 100644 index 0000000000..dd69df6772 --- /dev/null +++ b/test/parser/samples/error-else-if-without-if/error.json @@ -0,0 +1,6 @@ +{ + "code": "invalid-elseif-placement", + "message": "Cannot have an {:else if ...} block outside an {#if ...} block", + "start": { "line": 3, "column": 10, "character": 35 }, + "pos": 35 +} diff --git a/test/parser/samples/error-else-if-without-if/input.svelte b/test/parser/samples/error-else-if-without-if/input.svelte new file mode 100644 index 0000000000..06baf33511 --- /dev/null +++ b/test/parser/samples/error-else-if-without-if/input.svelte @@ -0,0 +1,4 @@ +{#await foo} +{:then bar} + {:else if} +{/await} \ No newline at end of file diff --git a/test/parser/samples/error-then-before-closing/error.json b/test/parser/samples/error-then-before-closing/error.json new file mode 100644 index 0000000000..07310c93fa --- /dev/null +++ b/test/parser/samples/error-then-before-closing/error.json @@ -0,0 +1,6 @@ +{ + "code": "invalid-then-placement", + "message": "Expected to close

  • tag before seeing {:then} block", + "start": { "line": 3, "column": 6, "character": 26 }, + "pos": 26 +} diff --git a/test/parser/samples/error-then-before-closing/input.svelte b/test/parser/samples/error-then-before-closing/input.svelte new file mode 100644 index 0000000000..bb0b993bdf --- /dev/null +++ b/test/parser/samples/error-then-before-closing/input.svelte @@ -0,0 +1,4 @@ +{#await true} +
  • +{:then f} +{/await} \ No newline at end of file diff --git a/test/parser/samples/event-handler/output.json b/test/parser/samples/event-handler/output.json index 44bb83de7d..95db9998f4 100644 --- a/test/parser/samples/event-handler/output.json +++ b/test/parser/samples/event-handler/output.json @@ -128,6 +128,16 @@ "type": "Identifier", "start": 68, "end": 75, + "loc": { + "start": { + "line": 3, + "column": 5 + }, + "end": { + "line": 3, + "column": 12 + } + }, "name": "visible" }, "children": [ diff --git a/test/parser/samples/if-block-else/output.json b/test/parser/samples/if-block-else/output.json index 8b22cfa26b..1f2cff3ff9 100644 --- a/test/parser/samples/if-block-else/output.json +++ b/test/parser/samples/if-block-else/output.json @@ -12,6 +12,16 @@ "type": "Identifier", "start": 5, "end": 8, + "loc": { + "start": { + "line": 1, + "column": 5 + }, + "end": { + "line": 1, + "column": 8 + } + }, "name": "foo" }, "children": [ diff --git a/test/parser/samples/if-block/output.json b/test/parser/samples/if-block/output.json index 1714bb7141..ef895a366a 100644 --- a/test/parser/samples/if-block/output.json +++ b/test/parser/samples/if-block/output.json @@ -12,6 +12,16 @@ "type": "Identifier", "start": 5, "end": 8, + "loc": { + "start": { + "line": 1, + "column": 5 + }, + "end": { + "line": 1, + "column": 8 + } + }, "name": "foo" }, "children": [ diff --git a/test/parser/samples/implicitly-closed-li-block/output.json b/test/parser/samples/implicitly-closed-li-block/output.json index 4a75a34223..767f64053c 100644 --- a/test/parser/samples/implicitly-closed-li-block/output.json +++ b/test/parser/samples/implicitly-closed-li-block/output.json @@ -40,6 +40,16 @@ "type": "Literal", "start": 18, "end": 22, + "loc": { + "start": { + "line": 3, + "column": 6 + }, + "end": { + "line": 3, + "column": 10 + } + }, "value": true, "raw": "true" }, diff --git a/test/parser/samples/no-error-if-before-closing/input.svelte b/test/parser/samples/no-error-if-before-closing/input.svelte new file mode 100644 index 0000000000..4059e0d4d1 --- /dev/null +++ b/test/parser/samples/no-error-if-before-closing/input.svelte @@ -0,0 +1,19 @@ +{#if true} + +{:else} +{/if} + +{#if true} +
    +{:else} +{/if} + +{#await true} + +{:then f} +{/await} + +{#await true} +
    +{:then f} +{/await} \ No newline at end of file diff --git a/test/parser/samples/no-error-if-before-closing/output.json b/test/parser/samples/no-error-if-before-closing/output.json new file mode 100644 index 0000000000..251cefc0ca --- /dev/null +++ b/test/parser/samples/no-error-if-before-closing/output.json @@ -0,0 +1,258 @@ +{ + "html": { + "start": 0, + "end": 148, + "type": "Fragment", + "children": [ + { + "start": 0, + "end": 33, + "type": "IfBlock", + "expression": { + "type": "Literal", + "start": 5, + "end": 9, + "loc": { + "start": { + "line": 1, + "column": 5 + }, + "end": { + "line": 1, + "column": 9 + } + }, + "value": true, + "raw": "true" + }, + "children": [ + { + "start": 12, + "end": 19, + "type": "Element", + "name": "input", + "attributes": [], + "children": [] + } + ], + "else": { + "start": 27, + "end": 28, + "type": "ElseBlock", + "children": [] + } + }, + { + "start": 33, + "end": 35, + "type": "Text", + "raw": "\n\n", + "data": "\n\n" + }, + { + "start": 35, + "end": 65, + "type": "IfBlock", + "expression": { + "type": "Literal", + "start": 40, + "end": 44, + "loc": { + "start": { + "line": 6, + "column": 5 + }, + "end": { + "line": 6, + "column": 9 + } + }, + "value": true, + "raw": "true" + }, + "children": [ + { + "start": 47, + "end": 51, + "type": "Element", + "name": "br", + "attributes": [], + "children": [] + } + ], + "else": { + "start": 59, + "end": 60, + "type": "ElseBlock", + "children": [] + } + }, + { + "start": 65, + "end": 67, + "type": "Text", + "raw": "\n\n", + "data": "\n\n" + }, + { + "start": 67, + "end": 108, + "type": "AwaitBlock", + "expression": { + "type": "Literal", + "start": 75, + "end": 79, + "loc": { + "start": { + "line": 11, + "column": 8 + }, + "end": { + "line": 11, + "column": 12 + } + }, + "value": true, + "raw": "true" + }, + "value": "f", + "error": null, + "pending": { + "start": 80, + "end": 90, + "type": "PendingBlock", + "children": [ + { + "start": 80, + "end": 82, + "type": "Text", + "raw": "\n\t", + "data": "\n\t" + }, + { + "start": 82, + "end": 89, + "type": "Element", + "name": "input", + "attributes": [], + "children": [] + }, + { + "start": 89, + "end": 90, + "type": "Text", + "raw": "\n", + "data": "\n" + } + ], + "skip": false + }, + "then": { + "start": 90, + "end": 100, + "type": "ThenBlock", + "children": [ + { + "start": 99, + "end": 100, + "type": "Text", + "raw": "\n", + "data": "\n" + } + ], + "skip": false + }, + "catch": { + "start": null, + "end": null, + "type": "CatchBlock", + "children": [], + "skip": true + } + }, + { + "start": 108, + "end": 110, + "type": "Text", + "raw": "\n\n", + "data": "\n\n" + }, + { + "start": 110, + "end": 148, + "type": "AwaitBlock", + "expression": { + "type": "Literal", + "start": 118, + "end": 122, + "loc": { + "start": { + "line": 16, + "column": 8 + }, + "end": { + "line": 16, + "column": 12 + } + }, + "value": true, + "raw": "true" + }, + "value": "f", + "error": null, + "pending": { + "start": 123, + "end": 130, + "type": "PendingBlock", + "children": [ + { + "start": 123, + "end": 125, + "type": "Text", + "raw": "\n\t", + "data": "\n\t" + }, + { + "start": 125, + "end": 129, + "type": "Element", + "name": "br", + "attributes": [], + "children": [] + }, + { + "start": 129, + "end": 130, + "type": "Text", + "raw": "\n", + "data": "\n" + } + ], + "skip": false + }, + "then": { + "start": 130, + "end": 140, + "type": "ThenBlock", + "children": [ + { + "start": 139, + "end": 140, + "type": "Text", + "raw": "\n", + "data": "\n" + } + ], + "skip": false + }, + "catch": { + "start": null, + "end": null, + "type": "CatchBlock", + "children": [], + "skip": true + } + } + ] + } +} \ No newline at end of file diff --git a/test/parser/samples/refs/output.json b/test/parser/samples/refs/output.json index 72a3d7689c..fb94ffd701 100644 --- a/test/parser/samples/refs/output.json +++ b/test/parser/samples/refs/output.json @@ -27,6 +27,16 @@ "type": "Identifier", "start": 49, "end": 52, + "loc": { + "start": { + "line": 5, + "column": 19 + }, + "end": { + "line": 5, + "column": 22 + } + }, "name": "foo" } } diff --git a/test/parser/samples/script-comment-only/output.json b/test/parser/samples/script-comment-only/output.json index 3441d8a7bb..e090b30ae6 100644 --- a/test/parser/samples/script-comment-only/output.json +++ b/test/parser/samples/script-comment-only/output.json @@ -41,7 +41,15 @@ } }, "body": [], - "sourceType": "module" + "sourceType": "module", + "trailingComments": [ + { + "type": "Line", + "value": " TODO write some code", + "start": 10, + "end": 33 + } + ] } } } \ No newline at end of file diff --git a/test/parser/samples/script-comment-trailing-multiline/output.json b/test/parser/samples/script-comment-trailing-multiline/output.json index 3c02b1fbde..184a29978d 100644 --- a/test/parser/samples/script-comment-trailing-multiline/output.json +++ b/test/parser/samples/script-comment-trailing-multiline/output.json @@ -33,6 +33,16 @@ "type": "Identifier", "start": 90, "end": 94, + "loc": { + "start": { + "line": 9, + "column": 11 + }, + "end": { + "line": 9, + "column": 15 + } + }, "name": "name" } }, @@ -134,7 +144,15 @@ "kind": "let" } ], - "sourceType": "module" + "sourceType": "module", + "trailingComments": [ + { + "type": "Block", + "value": "\n\ttrailing multiline comment\n", + "start": 32, + "end": 67 + } + ] } } } \ No newline at end of file diff --git a/test/parser/samples/script-comment-trailing/output.json b/test/parser/samples/script-comment-trailing/output.json index beca001042..2aca23f667 100644 --- a/test/parser/samples/script-comment-trailing/output.json +++ b/test/parser/samples/script-comment-trailing/output.json @@ -33,6 +33,16 @@ "type": "Identifier", "start": 79, "end": 83, + "loc": { + "start": { + "line": 7, + "column": 11 + }, + "end": { + "line": 7, + "column": 15 + } + }, "name": "name" } }, @@ -134,7 +144,15 @@ "kind": "let" } ], - "sourceType": "module" + "sourceType": "module", + "trailingComments": [ + { + "type": "Line", + "value": " trailing line comment", + "start": 32, + "end": 56 + } + ] } } } \ No newline at end of file diff --git a/test/parser/samples/script/output.json b/test/parser/samples/script/output.json index 00b7073a19..4d0aa9cf36 100644 --- a/test/parser/samples/script/output.json +++ b/test/parser/samples/script/output.json @@ -33,6 +33,16 @@ "type": "Identifier", "start": 52, "end": 56, + "loc": { + "start": { + "line": 5, + "column": 11 + }, + "end": { + "line": 5, + "column": 15 + } + }, "name": "name" } }, diff --git a/test/parser/samples/space-between-mustaches/output.json b/test/parser/samples/space-between-mustaches/output.json index 9a367bd2c1..b89f4d6c83 100644 --- a/test/parser/samples/space-between-mustaches/output.json +++ b/test/parser/samples/space-between-mustaches/output.json @@ -26,6 +26,16 @@ "type": "Identifier", "start": 5, "end": 6, + "loc": { + "start": { + "line": 1, + "column": 5 + }, + "end": { + "line": 1, + "column": 6 + } + }, "name": "a" } }, @@ -44,6 +54,16 @@ "type": "Identifier", "start": 9, "end": 10, + "loc": { + "start": { + "line": 1, + "column": 9 + }, + "end": { + "line": 1, + "column": 10 + } + }, "name": "b" } }, @@ -62,6 +82,16 @@ "type": "Identifier", "start": 15, "end": 16, + "loc": { + "start": { + "line": 1, + "column": 15 + }, + "end": { + "line": 1, + "column": 16 + } + }, "name": "c" } }, diff --git a/test/parser/samples/spread/output.json b/test/parser/samples/spread/output.json index 73a0dc9777..3a5c471905 100644 --- a/test/parser/samples/spread/output.json +++ b/test/parser/samples/spread/output.json @@ -18,6 +18,16 @@ "type": "Identifier", "start": 9, "end": 14, + "loc": { + "start": { + "line": 1, + "column": 9 + }, + "end": { + "line": 1, + "column": 14 + } + }, "name": "props" } } diff --git a/test/parser/samples/textarea-children/output.json b/test/parser/samples/textarea-children/output.json index 90f31f3caf..919fa33fde 100644 --- a/test/parser/samples/textarea-children/output.json +++ b/test/parser/samples/textarea-children/output.json @@ -29,6 +29,16 @@ "type": "Identifier", "start": 41, "end": 44, + "loc": { + "start": { + "line": 2, + "column": 30 + }, + "end": { + "line": 2, + "column": 33 + } + }, "name": "foo" } }, diff --git a/test/parser/samples/whitespace-normal/output.json b/test/parser/samples/whitespace-normal/output.json index acbae7ae17..61eca1329f 100644 --- a/test/parser/samples/whitespace-normal/output.json +++ b/test/parser/samples/whitespace-normal/output.json @@ -33,6 +33,16 @@ "type": "Identifier", "start": 19, "end": 23, + "loc": { + "start": { + "line": 1, + "column": 19 + }, + "end": { + "line": 1, + "column": 23 + } + }, "name": "name" } }, diff --git a/test/parser/samples/yield/input.svelte b/test/parser/samples/yield/input.svelte deleted file mode 100644 index f5e8aad053..0000000000 --- a/test/parser/samples/yield/input.svelte +++ /dev/null @@ -1 +0,0 @@ -{yield} \ No newline at end of file diff --git a/test/parser/samples/yield/output.json b/test/parser/samples/yield/output.json deleted file mode 100644 index b2e4b9430f..0000000000 --- a/test/parser/samples/yield/output.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "html": { - "start": 0, - "end": 7, - "type": "Fragment", - "children": [ - { - "start": 0, - "end": 7, - "type": "MustacheTag", - "expression": { - "type": "Identifier", - "start": 1, - "end": 6, - "name": "yield" - } - } - ] - } -} \ No newline at end of file diff --git a/test/runtime/index.js b/test/runtime/index.js index df02cabcb4..f070eb8185 100644 --- a/test/runtime/index.js +++ b/test/runtime/index.js @@ -10,6 +10,7 @@ import { showOutput, loadConfig, loadSvelte, + cleanRequireCache, env, setupHtmlEqual, mkdirp @@ -79,11 +80,7 @@ describe("runtime", () => { compileOptions.immutable = config.immutable; compileOptions.accessors = 'accessors' in config ? config.accessors : true; - Object.keys(require.cache) - .filter(x => x.endsWith('.svelte')) - .forEach(file => { - delete require.cache[file]; - }); + cleanRequireCache(); let mod; let SvelteComponent; diff --git a/test/runtime/samples/action-receives-element-mounted/_config.js b/test/runtime/samples/action-receives-element-mounted/_config.js new file mode 100644 index 0000000000..0806d0fa90 --- /dev/null +++ b/test/runtime/samples/action-receives-element-mounted/_config.js @@ -0,0 +1,8 @@ +const result = {}; + +export default { + props: { result }, + async test({ assert, component, target, window }) { + assert.notEqual(result.parentElement, null); + } +}; diff --git a/test/runtime/samples/action-receives-element-mounted/main.svelte b/test/runtime/samples/action-receives-element-mounted/main.svelte new file mode 100644 index 0000000000..a53ce81de0 --- /dev/null +++ b/test/runtime/samples/action-receives-element-mounted/main.svelte @@ -0,0 +1,8 @@ + + +

    Hello!

    \ No newline at end of file diff --git a/test/runtime/samples/binding-indirect-spread/_config.js b/test/runtime/samples/binding-indirect-spread/_config.js new file mode 100644 index 0000000000..1f24fcdbcb --- /dev/null +++ b/test/runtime/samples/binding-indirect-spread/_config.js @@ -0,0 +1,44 @@ +export default { + skip_if_ssr: true, + async test({ assert, component, target, window }) { + const event = new window.MouseEvent('click'); + + const [radio1, radio2, radio3] = target.querySelectorAll('input[type=radio]'); + + assert.ok(!radio1.checked); + assert.ok(radio2.checked); + assert.ok(!radio3.checked); + + component.radio = 'radio1'; + + assert.ok(radio1.checked); + assert.ok(!radio2.checked); + assert.ok(!radio3.checked); + + await radio3.dispatchEvent(event); + + assert.equal(component.radio, 'radio3'); + assert.ok(!radio1.checked); + assert.ok(!radio2.checked); + assert.ok(radio3.checked); + + const [check1, check2, check3] = target.querySelectorAll('input[type=checkbox]'); + + assert.ok(!check1.checked); + assert.ok(check2.checked); + assert.ok(!check3.checked); + + component.check = ['check1', 'check2']; + + assert.ok(check1.checked); + assert.ok(check2.checked); + assert.ok(!check3.checked); + + await check3.dispatchEvent(event); + + assert.deepEqual(component.check, ['check1', 'check2', 'check3']); + assert.ok(check1.checked); + assert.ok(check2.checked); + assert.ok(check3.checked); + } +}; diff --git a/test/runtime/samples/binding-indirect-spread/main.svelte b/test/runtime/samples/binding-indirect-spread/main.svelte new file mode 100644 index 0000000000..43129b08b7 --- /dev/null +++ b/test/runtime/samples/binding-indirect-spread/main.svelte @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/test/runtime/samples/component-binding-blowback-c/main.svelte b/test/runtime/samples/component-binding-blowback-c/main.svelte index 4876af13a8..75552bf358 100644 --- a/test/runtime/samples/component-binding-blowback-c/main.svelte +++ b/test/runtime/samples/component-binding-blowback-c/main.svelte @@ -4,7 +4,7 @@ export let count; export let idToValue = Object.create(null); - function ids() { + function ids(count) { return new Array(count) .fill(null) .map((_, i) => ({ id: 'id-' + i})) @@ -15,7 +15,7 @@
      - {#each ids() as object (object.id)} + {#each ids(count) as object (object.id)} {object.id}: value is {idToValue[object.id]} diff --git a/test/runtime/samples/component-slot-let-g/A.svelte b/test/runtime/samples/component-slot-let-g/A.svelte new file mode 100644 index 0000000000..4f4ac95014 --- /dev/null +++ b/test/runtime/samples/component-slot-let-g/A.svelte @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/test/runtime/samples/component-slot-let-g/_config.js b/test/runtime/samples/component-slot-let-g/_config.js new file mode 100644 index 0000000000..aaa9895ea8 --- /dev/null +++ b/test/runtime/samples/component-slot-let-g/_config.js @@ -0,0 +1,22 @@ +export default { + html: ` + 1 + 0 + `, + async test({ assert, target, component, window }) { + component.x = 2; + + assert.htmlEqual(target.innerHTML, ` + 2 + 0 + `); + + const span = target.querySelector('span'); + await span.dispatchEvent(new window.MouseEvent('click')); + + assert.htmlEqual(target.innerHTML, ` + 2 + 2 + `); + } +}; diff --git a/test/runtime/samples/component-slot-let-g/main.svelte b/test/runtime/samples/component-slot-let-g/main.svelte new file mode 100644 index 0000000000..e7d4890e6b --- /dev/null +++ b/test/runtime/samples/component-slot-let-g/main.svelte @@ -0,0 +1,17 @@ + + + + y = reflected} + slot="foo" + let:reflected + class={reflected} + > + {reflected} + + +{ y } \ No newline at end of file diff --git a/test/runtime/samples/component-slot-nested-if/Display.svelte b/test/runtime/samples/component-slot-nested-if/Display.svelte new file mode 100644 index 0000000000..d9034e4be2 --- /dev/null +++ b/test/runtime/samples/component-slot-nested-if/Display.svelte @@ -0,0 +1,2 @@ +Display: + \ No newline at end of file diff --git a/test/runtime/samples/component-slot-nested-if/Input.svelte b/test/runtime/samples/component-slot-nested-if/Input.svelte new file mode 100644 index 0000000000..fd8f22db00 --- /dev/null +++ b/test/runtime/samples/component-slot-nested-if/Input.svelte @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/test/runtime/samples/component-slot-nested-if/_config.js b/test/runtime/samples/component-slot-nested-if/_config.js new file mode 100644 index 0000000000..89dfd006cc --- /dev/null +++ b/test/runtime/samples/component-slot-nested-if/_config.js @@ -0,0 +1,30 @@ +export default { + html: ` + + `, + async test({ assert, target, snapshot, component, window }) { + const input = target.querySelector('input'); + + input.value = 'a'; + await input.dispatchEvent(new window.Event('input')); + + assert.htmlEqual( + target.innerHTML, + ` + + Display: a + ` + ); + + input.value = 'abc'; + await input.dispatchEvent(new window.Event('input')); + + assert.htmlEqual( + target.innerHTML, + ` + + Display: abc + ` + ); + }, +}; diff --git a/test/runtime/samples/component-slot-nested-if/main.svelte b/test/runtime/samples/component-slot-nested-if/main.svelte new file mode 100644 index 0000000000..727927b157 --- /dev/null +++ b/test/runtime/samples/component-slot-nested-if/main.svelte @@ -0,0 +1,10 @@ + + + + {#if foo} + {foo} + {/if} + diff --git a/test/runtime/samples/dev-warning-each-block-no-sets-maps/_config.js b/test/runtime/samples/dev-warning-each-block-no-sets-maps/_config.js new file mode 100644 index 0000000000..83481d9ebe --- /dev/null +++ b/test/runtime/samples/dev-warning-each-block-no-sets-maps/_config.js @@ -0,0 +1,6 @@ +export default { + compileOptions: { + dev: true + }, + error: `{#each} only iterates over array-like objects. You can use a spread to convert this iterable into an array.` +}; diff --git a/test/runtime/samples/dev-warning-each-block-no-sets-maps/main.svelte b/test/runtime/samples/dev-warning-each-block-no-sets-maps/main.svelte new file mode 100644 index 0000000000..e1aae26d12 --- /dev/null +++ b/test/runtime/samples/dev-warning-each-block-no-sets-maps/main.svelte @@ -0,0 +1,7 @@ + + +{#each foo as item} +
      {item}
      +{/each} \ No newline at end of file diff --git a/test/runtime/samples/dev-warning-each-block-require-arraylike/_config.js b/test/runtime/samples/dev-warning-each-block-require-arraylike/_config.js new file mode 100644 index 0000000000..62e5fc209b --- /dev/null +++ b/test/runtime/samples/dev-warning-each-block-require-arraylike/_config.js @@ -0,0 +1,6 @@ +export default { + compileOptions: { + dev: true + }, + error: `{#each} only iterates over array-like objects.` +}; diff --git a/test/runtime/samples/dev-warning-each-block-require-arraylike/main.svelte b/test/runtime/samples/dev-warning-each-block-require-arraylike/main.svelte new file mode 100644 index 0000000000..f85df8d84b --- /dev/null +++ b/test/runtime/samples/dev-warning-each-block-require-arraylike/main.svelte @@ -0,0 +1,3 @@ +{#each {} as item} +
      {item}
      +{/each} \ No newline at end of file diff --git a/test/runtime/samples/each-block-destructured-object-reserved-key/_config.js b/test/runtime/samples/each-block-destructured-object-reserved-key/_config.js new file mode 100644 index 0000000000..c04e984691 --- /dev/null +++ b/test/runtime/samples/each-block-destructured-object-reserved-key/_config.js @@ -0,0 +1,5 @@ +export default { + html: ` +

      bar

      + ` +}; diff --git a/test/runtime/samples/each-block-destructured-object-reserved-key/main.svelte b/test/runtime/samples/each-block-destructured-object-reserved-key/main.svelte new file mode 100644 index 0000000000..c3e11c3ea1 --- /dev/null +++ b/test/runtime/samples/each-block-destructured-object-reserved-key/main.svelte @@ -0,0 +1,7 @@ + + +{#each foo as { in: bar }} +

      {bar}

      +{/each} diff --git a/test/runtime/samples/each-block-keyed-dynamic-2/_config.js b/test/runtime/samples/each-block-keyed-dynamic-2/_config.js new file mode 100644 index 0000000000..7eba75a5ff --- /dev/null +++ b/test/runtime/samples/each-block-keyed-dynamic-2/_config.js @@ -0,0 +1,23 @@ +export default { + html: ` + + 0 +
        + `, + + async test({ assert, component, target, window }) { + const button = target.querySelector("button"); + + const event = new window.MouseEvent("click"); + await button.dispatchEvent(event); + + assert.htmlEqual( + target.innerHTML, + ` + + 1 +
          + ` + ); + } +}; diff --git a/test/runtime/samples/each-block-keyed-dynamic-2/main.svelte b/test/runtime/samples/each-block-keyed-dynamic-2/main.svelte new file mode 100644 index 0000000000..ab455a585e --- /dev/null +++ b/test/runtime/samples/each-block-keyed-dynamic-2/main.svelte @@ -0,0 +1,20 @@ + + + +{num} +
            + {#each cards as c, i (i)} +
          • {c}
          • + {/each} +
          diff --git a/test/runtime/samples/event-handler-dynamic-2/_config.js b/test/runtime/samples/event-handler-dynamic-2/_config.js new file mode 100644 index 0000000000..c996d8f2aa --- /dev/null +++ b/test/runtime/samples/event-handler-dynamic-2/_config.js @@ -0,0 +1,33 @@ +export default { + html: ` + +

          0

          + + + `, + + async test({ assert, target, window }) { + const [toggle, handler_a, handler_b] = target.querySelectorAll('button'); + const p = target.querySelector('p'); + + const event = new window.MouseEvent('click'); + + await handler_a.dispatchEvent(event); + assert.equal(p.innerHTML, '1'); + + await toggle.dispatchEvent(event); + + await handler_a.dispatchEvent(event); + assert.equal(p.innerHTML, '2'); + + await toggle.dispatchEvent(event); + + await handler_b.dispatchEvent(event); + assert.equal(p.innerHTML, '1'); + + await toggle.dispatchEvent(event); + + await handler_b.dispatchEvent(event); + assert.equal(p.innerHTML, '2'); + }, +}; diff --git a/test/runtime/samples/event-handler-dynamic-2/main.svelte b/test/runtime/samples/event-handler-dynamic-2/main.svelte new file mode 100644 index 0000000000..1b2041d3b8 --- /dev/null +++ b/test/runtime/samples/event-handler-dynamic-2/main.svelte @@ -0,0 +1,20 @@ + + + + +

          {number}

          + + + diff --git a/test/runtime/samples/event-handler-modifier-body-once/_config.js b/test/runtime/samples/event-handler-modifier-body-once/_config.js new file mode 100644 index 0000000000..4127034010 --- /dev/null +++ b/test/runtime/samples/event-handler-modifier-body-once/_config.js @@ -0,0 +1,11 @@ +export default { + async test({ assert, component, window }) { + const event = new window.MouseEvent('click'); + + await window.document.body.dispatchEvent(event); + assert.equal(component.count, 1); + + await window.document.body.dispatchEvent(event); + assert.equal(component.count, 1); + } +}; diff --git a/test/runtime/samples/event-handler-modifier-body-once/main.svelte b/test/runtime/samples/event-handler-modifier-body-once/main.svelte new file mode 100644 index 0000000000..423a75d1f0 --- /dev/null +++ b/test/runtime/samples/event-handler-modifier-body-once/main.svelte @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/test/runtime/samples/instrumentation-update-expression/_config.js b/test/runtime/samples/instrumentation-update-expression/_config.js new file mode 100644 index 0000000000..cc33422f6f --- /dev/null +++ b/test/runtime/samples/instrumentation-update-expression/_config.js @@ -0,0 +1,31 @@ +export default { + html: ` +

          0

          + + +

          0

          + + + `, + async test({ assert, target, window }) { + const [foo, bar] = target.querySelectorAll('p'); + const [button1, button2, button3, button4] = target.querySelectorAll('button'); + const event = new window.MouseEvent('click'); + + await button1.dispatchEvent(event); + assert.equal(foo.innerHTML, '1'); + assert.equal(bar.innerHTML, '0'); + + await button2.dispatchEvent(event); + assert.equal(foo.innerHTML, '2'); + assert.equal(bar.innerHTML, '0'); + + await button3.dispatchEvent(event); + assert.equal(foo.innerHTML, '2'); + assert.equal(bar.innerHTML, '1'); + + await button4.dispatchEvent(event); + assert.equal(foo.innerHTML, '2'); + assert.equal(bar.innerHTML, '2'); + } +}; diff --git a/test/runtime/samples/instrumentation-update-expression/main.svelte b/test/runtime/samples/instrumentation-update-expression/main.svelte new file mode 100644 index 0000000000..0672f6330d --- /dev/null +++ b/test/runtime/samples/instrumentation-update-expression/main.svelte @@ -0,0 +1,14 @@ + + +

          {foo}

          + + + + +

          {bar.bar}

          + + + diff --git a/test/runtime/samples/keyed-each-dev-unique/_config.js b/test/runtime/samples/keyed-each-dev-unique/_config.js new file mode 100644 index 0000000000..8f46af9d52 --- /dev/null +++ b/test/runtime/samples/keyed-each-dev-unique/_config.js @@ -0,0 +1,7 @@ +export default { + compileOptions: { + dev: true + }, + + error: `Cannot have duplicate keys in a keyed each` +}; diff --git a/test/runtime/samples/keyed-each-dev-unique/main.svelte b/test/runtime/samples/keyed-each-dev-unique/main.svelte new file mode 100644 index 0000000000..870e7beaa1 --- /dev/null +++ b/test/runtime/samples/keyed-each-dev-unique/main.svelte @@ -0,0 +1,7 @@ + + +{#each array as item (item)} + {item} +{/each} diff --git a/test/runtime/samples/lifecycle-onmount-infinite-loop/Child.svelte b/test/runtime/samples/lifecycle-onmount-infinite-loop/Child.svelte new file mode 100644 index 0000000000..ef16875b64 --- /dev/null +++ b/test/runtime/samples/lifecycle-onmount-infinite-loop/Child.svelte @@ -0,0 +1 @@ +Child diff --git a/test/runtime/samples/lifecycle-onmount-infinite-loop/_config.js b/test/runtime/samples/lifecycle-onmount-infinite-loop/_config.js new file mode 100644 index 0000000000..a76a2686ac --- /dev/null +++ b/test/runtime/samples/lifecycle-onmount-infinite-loop/_config.js @@ -0,0 +1,6 @@ +export default { + test({ assert, component }) { + const { count } = component; + assert.deepEqual(count, 1); + } +}; diff --git a/test/runtime/samples/lifecycle-onmount-infinite-loop/main.svelte b/test/runtime/samples/lifecycle-onmount-infinite-loop/main.svelte new file mode 100644 index 0000000000..1fa4263e39 --- /dev/null +++ b/test/runtime/samples/lifecycle-onmount-infinite-loop/main.svelte @@ -0,0 +1,16 @@ + + +
          diff --git a/test/runtime/samples/module-context-bind/_config.js b/test/runtime/samples/module-context-bind/_config.js new file mode 100644 index 0000000000..32bc9c4ce9 --- /dev/null +++ b/test/runtime/samples/module-context-bind/_config.js @@ -0,0 +1,4 @@ +export default { + skip_if_ssr: true, + html: '
          object
          ' +}; diff --git a/test/runtime/samples/module-context-bind/main.svelte b/test/runtime/samples/module-context-bind/main.svelte new file mode 100644 index 0000000000..1580102bd8 --- /dev/null +++ b/test/runtime/samples/module-context-bind/main.svelte @@ -0,0 +1,11 @@ + + + + +
          {typeof bar}
          diff --git a/test/runtime/samples/reactive-values-no-implicit-member-expression/_config.js b/test/runtime/samples/reactive-values-no-implicit-member-expression/_config.js new file mode 100644 index 0000000000..05a8159589 --- /dev/null +++ b/test/runtime/samples/reactive-values-no-implicit-member-expression/_config.js @@ -0,0 +1,5 @@ +export default { + test({ assert, window }) { + assert.equal(window.document.title, 'foo'); + } +}; diff --git a/test/runtime/samples/reactive-values-no-implicit-member-expression/main.svelte b/test/runtime/samples/reactive-values-no-implicit-member-expression/main.svelte new file mode 100644 index 0000000000..a631d4d9f8 --- /dev/null +++ b/test/runtime/samples/reactive-values-no-implicit-member-expression/main.svelte @@ -0,0 +1,3 @@ + diff --git a/test/runtime/samples/reactive-values-store-destructured-undefined/_config.js b/test/runtime/samples/reactive-values-store-destructured-undefined/_config.js new file mode 100644 index 0000000000..5e3f15f5dc --- /dev/null +++ b/test/runtime/samples/reactive-values-store-destructured-undefined/_config.js @@ -0,0 +1,6 @@ +export default { + html: ` +

          undefined

          +

          undefined

          + ` +}; diff --git a/test/runtime/samples/reactive-values-store-destructured-undefined/main.svelte b/test/runtime/samples/reactive-values-store-destructured-undefined/main.svelte new file mode 100644 index 0000000000..152e0e3e2e --- /dev/null +++ b/test/runtime/samples/reactive-values-store-destructured-undefined/main.svelte @@ -0,0 +1,9 @@ + + +

          {foo1}

          +

          {foo2}

          diff --git a/test/runtime/samples/store-auto-subscribe-nullish/_config.js b/test/runtime/samples/store-auto-subscribe-nullish/_config.js new file mode 100644 index 0000000000..52e21cef05 --- /dev/null +++ b/test/runtime/samples/store-auto-subscribe-nullish/_config.js @@ -0,0 +1,13 @@ +import { writable } from '../../../../store'; + +export default { + html: ` +

          undefined

          + `, + async test({ assert, component, target }) { + component.store = writable('foo'); + assert.htmlEqual(target.innerHTML, ` +

          foo

          + `); + } +}; diff --git a/test/runtime/samples/store-auto-subscribe-nullish/main.svelte b/test/runtime/samples/store-auto-subscribe-nullish/main.svelte new file mode 100644 index 0000000000..9c1ed4a560 --- /dev/null +++ b/test/runtime/samples/store-auto-subscribe-nullish/main.svelte @@ -0,0 +1,5 @@ + + +

          {$store}

          diff --git a/test/server-side-rendering/index.js b/test/server-side-rendering/index.js index a56a4ddaed..ee1319845d 100644 --- a/test/server-side-rendering/index.js +++ b/test/server-side-rendering/index.js @@ -9,6 +9,7 @@ import { loadSvelte, setupHtmlEqual, tryToLoadJson, + cleanRequireCache, shouldUpdateExpected, mkdirp } from "../helpers.js"; @@ -27,11 +28,6 @@ let compile = null; describe("ssr", () => { before(() => { - require("../../register")({ - extensions: ['.svelte', '.html'], - sveltePath - }); - compile = loadSvelte(true).compile; return setupHtmlEqual(); @@ -40,9 +36,11 @@ describe("ssr", () => { fs.readdirSync(`${__dirname}/samples`).forEach(dir => { if (dir[0] === ".") return; + const config = loadConfig(`${__dirname}/samples/${dir}/_config.js`); + // add .solo to a sample directory name to only run that test, or // .show to always show the output. or both - const solo = /\.solo/.test(dir); + const solo = config.solo || /\.solo/.test(dir); const show = /\.show/.test(dir); if (solo && process.env.CI) { @@ -51,6 +49,18 @@ describe("ssr", () => { (solo ? it.only : it)(dir, () => { dir = path.resolve(`${__dirname}/samples`, dir); + + cleanRequireCache(); + + const compileOptions = { + sveltePath, + ...config.compileOptions, + generate: 'ssr', + format: 'cjs' + }; + + require("../../register")(compileOptions); + try { const Component = require(`${dir}/main.svelte`).default; @@ -133,18 +143,16 @@ describe("ssr", () => { (config.skip ? it.skip : solo ? it.only : it)(dir, () => { const cwd = path.resolve("test/runtime/samples", dir); - Object.keys(require.cache) - .filter(x => x.endsWith('.svelte')) - .forEach(file => { - delete require.cache[file]; - }); + cleanRequireCache(); delete global.window; - const compileOptions = Object.assign({ sveltePath }, config.compileOptions, { + const compileOptions = { + sveltePath, + ...config.compileOptions, generate: 'ssr', format: 'cjs' - }); + }; require("../../register")(compileOptions); diff --git a/test/server-side-rendering/samples/head-meta-hydrate-duplicate/_config.js b/test/server-side-rendering/samples/head-meta-hydrate-duplicate/_config.js new file mode 100644 index 0000000000..ae9b250f86 --- /dev/null +++ b/test/server-side-rendering/samples/head-meta-hydrate-duplicate/_config.js @@ -0,0 +1,5 @@ +export default { + compileOptions: { + hydratable: true + } +}; diff --git a/test/server-side-rendering/samples/head-meta-hydrate-duplicate/_expected-head.html b/test/server-side-rendering/samples/head-meta-hydrate-duplicate/_expected-head.html new file mode 100644 index 0000000000..107753cdd0 --- /dev/null +++ b/test/server-side-rendering/samples/head-meta-hydrate-duplicate/_expected-head.html @@ -0,0 +1,4 @@ +Some Title + + + \ No newline at end of file diff --git a/test/server-side-rendering/samples/head-meta-hydrate-duplicate/_expected.html b/test/server-side-rendering/samples/head-meta-hydrate-duplicate/_expected.html new file mode 100644 index 0000000000..a469e618fa --- /dev/null +++ b/test/server-side-rendering/samples/head-meta-hydrate-duplicate/_expected.html @@ -0,0 +1,3 @@ + + +
          Just a dummy page.
          \ No newline at end of file diff --git a/test/server-side-rendering/samples/head-meta-hydrate-duplicate/main.svelte b/test/server-side-rendering/samples/head-meta-hydrate-duplicate/main.svelte new file mode 100644 index 0000000000..1a8b125dd2 --- /dev/null +++ b/test/server-side-rendering/samples/head-meta-hydrate-duplicate/main.svelte @@ -0,0 +1,8 @@ + + Some Title + + + + + +
          Just a dummy page.
          \ No newline at end of file diff --git a/test/server-side-rendering/samples/head-multiple-title/A.svelte b/test/server-side-rendering/samples/head-multiple-title/A.svelte new file mode 100644 index 0000000000..b139b4ff77 --- /dev/null +++ b/test/server-side-rendering/samples/head-multiple-title/A.svelte @@ -0,0 +1,3 @@ + + A + diff --git a/test/server-side-rendering/samples/head-multiple-title/B.svelte b/test/server-side-rendering/samples/head-multiple-title/B.svelte new file mode 100644 index 0000000000..4a29ecb04c --- /dev/null +++ b/test/server-side-rendering/samples/head-multiple-title/B.svelte @@ -0,0 +1,3 @@ + + B + diff --git a/test/server-side-rendering/samples/head-multiple-title/_expected-head.html b/test/server-side-rendering/samples/head-multiple-title/_expected-head.html new file mode 100644 index 0000000000..af5c5feba4 --- /dev/null +++ b/test/server-side-rendering/samples/head-multiple-title/_expected-head.html @@ -0,0 +1 @@ +B \ No newline at end of file diff --git a/test/server-side-rendering/samples/head-multiple-title/_expected.html b/test/server-side-rendering/samples/head-multiple-title/_expected.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/server-side-rendering/samples/head-multiple-title/data.json b/test/server-side-rendering/samples/head-multiple-title/data.json new file mode 100644 index 0000000000..eab238e816 --- /dev/null +++ b/test/server-side-rendering/samples/head-multiple-title/data.json @@ -0,0 +1,3 @@ +{ + "adjective": "custom" +} \ No newline at end of file diff --git a/test/server-side-rendering/samples/head-multiple-title/main.svelte b/test/server-side-rendering/samples/head-multiple-title/main.svelte new file mode 100644 index 0000000000..fb9a70b923 --- /dev/null +++ b/test/server-side-rendering/samples/head-multiple-title/main.svelte @@ -0,0 +1,10 @@ + + + + Main + + + diff --git a/test/server-side-rendering/samples/spread-attributes-white-space/_expected.html b/test/server-side-rendering/samples/spread-attributes-white-space/_expected.html new file mode 100644 index 0000000000..a73bb17e8c --- /dev/null +++ b/test/server-side-rendering/samples/spread-attributes-white-space/_expected.html @@ -0,0 +1,8 @@ + + + \ No newline at end of file diff --git a/test/server-side-rendering/samples/spread-attributes-white-space/main.svelte b/test/server-side-rendering/samples/spread-attributes-white-space/main.svelte new file mode 100644 index 0000000000..6919d9ea54 --- /dev/null +++ b/test/server-side-rendering/samples/spread-attributes-white-space/main.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/test/store/index.js b/test/store/index.js index a39fab86e6..2af4a6f35d 100644 --- a/test/store/index.js +++ b/test/store/index.js @@ -116,6 +116,15 @@ describe('store', () => { }); }); + const fake_observable = { + subscribe(fn) { + fn(42); + return { + unsubscribe: () => {} + }; + } + }; + describe('derived', () => { it('maps a single store', () => { const a = writable(1); @@ -346,6 +355,11 @@ describe('store', () => { b.set(2); assert.deepEqual(get(c), 'two 2'); }); + + it('works with RxJS-style observables', () => { + const d = derived(fake_observable, _ => _); + assert.equal(get(d), 42); + }); }); describe('get', () => { @@ -355,16 +369,7 @@ describe('store', () => { }); it('works with RxJS-style observables', () => { - const observable = { - subscribe(fn) { - fn(42); - return { - unsubscribe: () => {} - }; - } - }; - - assert.equal(get(observable), 42); + assert.equal(get(fake_observable), 42); }); }); }); diff --git a/test/validator/index.js b/test/validator/index.js index 8bf0400d7c..9bce5e149b 100644 --- a/test/validator/index.js +++ b/test/validator/index.js @@ -20,6 +20,7 @@ describe("validate", () => { const input = fs.readFileSync(`${__dirname}/samples/${dir}/input.svelte`, "utf-8").replace(/\s+$/, ""); const expected_warnings = tryToLoadJson(`${__dirname}/samples/${dir}/warnings.json`) || []; const expected_errors = tryToLoadJson(`${__dirname}/samples/${dir}/errors.json`); + const options = tryToLoadJson(`${__dirname}/samples/${dir}/options.json`); let error; @@ -28,7 +29,8 @@ describe("validate", () => { dev: config.dev, legacy: config.legacy, generate: false, - customElement: config.customElement + customElement: config.customElement, + ...options, }); assert.deepEqual(warnings.map(w => ({ diff --git a/test/validator/samples/binding-await-catch/errors.json b/test/validator/samples/binding-await-catch/errors.json new file mode 100644 index 0000000000..00141686f3 --- /dev/null +++ b/test/validator/samples/binding-await-catch/errors.json @@ -0,0 +1,9 @@ +[ + { + "code": "invalid-binding", + "message": "Cannot bind to a variable declared with {#await ... then} or {:catch} blocks", + "pos": 79, + "start": { "line": 6, "column": 9, "character": 79 }, + "end": { "line": 6, "column": 27, "character": 97 } + } +] diff --git a/test/validator/samples/binding-await-catch/input.svelte b/test/validator/samples/binding-await-catch/input.svelte new file mode 100644 index 0000000000..b640f6305b --- /dev/null +++ b/test/validator/samples/binding-await-catch/input.svelte @@ -0,0 +1,7 @@ + +{#await promise} +{:catch error} + +{/await} diff --git a/test/validator/samples/binding-await-then-2/errors.json b/test/validator/samples/binding-await-then-2/errors.json new file mode 100644 index 0000000000..e734ed4717 --- /dev/null +++ b/test/validator/samples/binding-await-then-2/errors.json @@ -0,0 +1,9 @@ +[ + { + "code": "invalid-binding", + "message": "Cannot bind to a variable declared with {#await ... then} or {:catch} blocks", + "pos": 78, + "start": { "line": 6, "column": 9, "character": 78 }, + "end": { "line": 6, "column": 19, "character": 88 } + } +] diff --git a/test/validator/samples/binding-await-then-2/input.svelte b/test/validator/samples/binding-await-then-2/input.svelte new file mode 100644 index 0000000000..e8c56c8e0b --- /dev/null +++ b/test/validator/samples/binding-await-then-2/input.svelte @@ -0,0 +1,7 @@ + +{#await promise} +{:then value} + +{/await} diff --git a/test/validator/samples/binding-await-then/errors.json b/test/validator/samples/binding-await-then/errors.json new file mode 100644 index 0000000000..a611e7731f --- /dev/null +++ b/test/validator/samples/binding-await-then/errors.json @@ -0,0 +1,9 @@ +[ + { + "code": "invalid-binding", + "message": "Cannot bind to a variable declared with {#await ... then} or {:catch} blocks", + "pos": 75, + "start": { "line": 5, "column": 9, "character": 75 }, + "end": { "line": 5, "column": 19, "character": 85 } + } +] diff --git a/test/validator/samples/binding-await-then/input.svelte b/test/validator/samples/binding-await-then/input.svelte new file mode 100644 index 0000000000..bc55ef97e4 --- /dev/null +++ b/test/validator/samples/binding-await-then/input.svelte @@ -0,0 +1,6 @@ + +{#await promise then value} + +{/await} diff --git a/test/validator/samples/component-dynamic/input.svelte b/test/validator/samples/component-dynamic/input.svelte new file mode 100644 index 0000000000..f591a84fe1 --- /dev/null +++ b/test/validator/samples/component-dynamic/input.svelte @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/test/validator/samples/component-dynamic/options.json b/test/validator/samples/component-dynamic/options.json new file mode 100644 index 0000000000..3a50892eba --- /dev/null +++ b/test/validator/samples/component-dynamic/options.json @@ -0,0 +1,3 @@ +{ + "generate": true +} \ No newline at end of file diff --git a/test/validator/samples/component-dynamic/warnings.json b/test/validator/samples/component-dynamic/warnings.json new file mode 100644 index 0000000000..457ef11a1e --- /dev/null +++ b/test/validator/samples/component-dynamic/warnings.json @@ -0,0 +1,47 @@ +[ + { + "code": "reactive-component", + "message": " will not be reactive if Let changes. Use if you want this reactivity.", + "pos": 190, + "end": { + "character": 197, + "column": 7, + "line": 15 + }, + "start": { + "character": 190, + "column": 0, + "line": 15 + } + }, + { + "message": " will not be reactive if ExportLet changes. Use if you want this reactivity.", + "code": "reactive-component", + "pos": 198, + "end": { + "character": 211, + "column": 13, + "line": 16 + }, + "start": { + "character": 198, + "column": 0, + "line": 16 + } + }, + { + "message": " will not be reactive if Reactive changes. Use if you want this reactivity.", + "code": "reactive-component", + "pos": 212, + "end": { + "character": 224, + "column": 12, + "line": 17 + }, + "start": { + "character": 212, + "column": 0, + "line": 17 + } + } +] diff --git a/test/validator/samples/each-block-invalid-context-destructured-object/errors.json b/test/validator/samples/each-block-invalid-context-destructured-object/errors.json new file mode 100644 index 0000000000..085021ff5a --- /dev/null +++ b/test/validator/samples/each-block-invalid-context-destructured-object/errors.json @@ -0,0 +1,15 @@ +[{ + "code": "unexpected-reserved-word", + "message": "'case' is a reserved word in JavaScript and cannot be used here", + "start": { + "line": 1, + "column": 18, + "character": 18 + }, + "end": { + "line": 1, + "column": 18, + "character": 18 + }, + "pos": 18 +}] diff --git a/test/validator/samples/each-block-invalid-context-destructured-object/input.svelte b/test/validator/samples/each-block-invalid-context-destructured-object/input.svelte new file mode 100644 index 0000000000..a891f131a0 --- /dev/null +++ b/test/validator/samples/each-block-invalid-context-destructured-object/input.svelte @@ -0,0 +1,3 @@ +{#each cases as { case }} + {case.title} +{/each} diff --git a/test/validator/samples/unreferenced-variables/warnings.json b/test/validator/samples/unreferenced-variables/warnings.json index 7d9e0111bf..dfac58ebdb 100644 --- a/test/validator/samples/unreferenced-variables/warnings.json +++ b/test/validator/samples/unreferenced-variables/warnings.json @@ -6,7 +6,7 @@ "column": 12, "line": 8 }, - "message": "Component has unused export property 'd'. If it is for external reference only, please consider using `export const 'd'`", + "message": "Component has unused export property 'd'. If it is for external reference only, please consider using `export const d`", "pos": 102, "start": { "character": 102, @@ -21,7 +21,7 @@ "column": 15, "line": 8 }, - "message": "Component has unused export property 'e'. If it is for external reference only, please consider using `export const 'e'`", + "message": "Component has unused export property 'e'. If it is for external reference only, please consider using `export const e`", "pos": 105, "start": { "character": 105, @@ -36,7 +36,7 @@ "column": 18, "line": 9 }, - "message": "Component has unused export property 'g'. If it is for external reference only, please consider using `export const 'g'`", + "message": "Component has unused export property 'g'. If it is for external reference only, please consider using `export const g`", "pos": 125, "start": { "character": 125, @@ -51,7 +51,7 @@ "column": 18, "line": 10 }, - "message": "Component has unused export property 'h'. If it is for external reference only, please consider using `export const 'h'`", + "message": "Component has unused export property 'h'. If it is for external reference only, please consider using `export const h`", "pos": 145, "start": { "character": 145, @@ -66,7 +66,7 @@ "column": 25, "line": 12 }, - "message": "Component has unused export property 'j'. If it is for external reference only, please consider using `export const 'j'`", + "message": "Component has unused export property 'j'. If it is for external reference only, please consider using `export const j`", "pos": 187, "start": { "character": 187,