Merge branch 'sveltejs:master' into master

pull/6453/head
Jatin Hemnani 5 years ago committed by GitHub
commit 68203da4cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,5 +1,6 @@
### Before submitting the PR, please make sure you do the following
- [ ] It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
- [ ] Prefix your PR title with `[feat]`, `[fix]`, `[chore]`, or `[docs]`.
- [ ] This message body should clearly illustrate what problems it solves.
- [ ] Ideally, include a test that fails without this PR but passes with it.

1
.gitignore vendored

@ -8,6 +8,7 @@ node_modules
/compiler.d.ts
/compiler.*js
/index.*js
/ssr.*js
/internal
/store
/easing

@ -1,5 +1,16 @@
# Svelte changelog
## Unreleased
* Add `trusted` event modifier ([#6137](https://github.com/sveltejs/svelte/issues/6137))
* Fix erroneous `unknown prop` warning when using slot on a component ([#6065](https://github.com/sveltejs/svelte/pull/6065))
* Throw compiler error when passing empty directive names ([#6299](https://github.com/sveltejs/svelte/issues/6299))
* New a11y warning `a11y-mouse-events-have-key-events` which checks that `mouseover`/`mouseout` are accompanied by `focus`/`blur` event handlers ([5938](https://github.com/sveltejs/svelte/pull/5938))
* Fix `preserveComments` in SSR mode ([4730](https://github.com/sveltejs/svelte/issues/4730))
* Add `varsReport` compiler option ([#6192](https://github.com/sveltejs/svelte/pull/6192))
* Expose `svelte/ssr` which exported lifecycle methods as noop ([#6416](https://github.com/sveltejs/svelte/pull/6416))
* Fix compiler error when using `:where()` inside `:global()` ([#6434](https://github.com/sveltejs/svelte/issues/6434))
## 3.38.3
* Speed up hydration by reducing amount of element reorderings ([#4308](https://github.com/sveltejs/svelte/issues/4308))

@ -12,7 +12,7 @@ The [Open Source Guides](https://opensource.guide/) website has a collection of
There are many ways to contribute to Svelte, and many of them do not involve writing any code. Here's a few ideas to get started:
- Simply start using Svelte. Go through the [Getting Started](https://svelte.dev/blog/the-easiest-way-to-get-started) guide. Does everything work as expected? If not, we're always looking for improvements. Let us know by [opening an issue](#reporting-new-issues).
- Look through the [open issues](https://github.com/sveltejs/svelte/issues). Provide workarounds, ask for clarification, or suggest labels. Help [triage issues](#triaging-issues-and-pull-requests).
- Look through the [open issues](https://github.com/sveltejs/svelte/issues). A good starting point would be issues tagged [good first issue](https://github.com/sveltejs/svelte/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22). Provide workarounds, ask for clarification, or suggest labels. Help [triage issues](#triaging-issues-and-pull-requests).
- If you find an issue you would like to fix, [open a pull request](#your-first-pull-request).
- Read through our [tutorials](https://svelte.dev/tutorial/basics). If you find anything that is confusing or can be improved, you can make edits by clicking "Edit this chapter" at the bottom left of the tutorial page.
- Take a look at the [features requested](https://github.com/sveltejs/svelte/labels/enhancement) by others in the community and consider opening a pull request if you see something you want to work on.

33
package-lock.json generated

@ -956,6 +956,24 @@
"is-reference": "^1.1.4",
"periscopic": "^2.0.1",
"sourcemap-codec": "^1.4.6"
},
"dependencies": {
"estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"dev": true
},
"periscopic": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/periscopic/-/periscopic-2.0.3.tgz",
"integrity": "sha512-FuCZe61mWxQOJAQFEfmt9FjzebRlcpFz8sFPbyaCKtdusPkMEbA9ey0eARnRav5zAhmXznhaQkKGFAPn7X9NUw==",
"dev": true,
"requires": {
"estree-walker": "^2.0.2",
"is-reference": "^1.1.4"
}
}
}
},
"codecov": {
@ -3440,12 +3458,21 @@
"dev": true
},
"periscopic": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/periscopic/-/periscopic-2.0.1.tgz",
"integrity": "sha512-twJ8e4RatllMAcbmBqKj8cvZ94HtqSzbb8hJoGj4iSCcCHXxKb06HRxOq4heyq2x/6mKynJDvTTreHCz+m6lJw==",
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/periscopic/-/periscopic-2.0.3.tgz",
"integrity": "sha512-FuCZe61mWxQOJAQFEfmt9FjzebRlcpFz8sFPbyaCKtdusPkMEbA9ey0eARnRav5zAhmXznhaQkKGFAPn7X9NUw==",
"dev": true,
"requires": {
"estree-walker": "^2.0.2",
"is-reference": "^1.1.4"
},
"dependencies": {
"estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"dev": true
}
}
},
"picomatch": {

@ -54,6 +54,10 @@
"./transition": {
"import": "./transition/index.mjs",
"require": "./transition/index.js"
},
"./ssr": {
"import": "./ssr.mjs",
"require": "./ssr.js"
}
},
"engines": {
@ -123,7 +127,7 @@
"locate-character": "^2.0.5",
"magic-string": "^0.25.3",
"mocha": "^7.0.0",
"periscopic": "^2.0.1",
"periscopic": "^2.0.3",
"puppeteer": "^2.1.1",
"rollup": "^1.27.14",
"source-map": "^0.7.3",

@ -42,6 +42,24 @@ export default [
plugins: [ts_plugin]
},
{
input: `src/runtime/ssr.ts`,
output: [
{
file: `ssr.mjs`,
format: 'esm',
paths: id => id.startsWith('svelte/') && `${id.replace('svelte', '.')}/index.mjs`
},
{
file: `ssr.js`,
format: 'cjs',
paths: id => id.startsWith('svelte/') && `${id.replace('svelte', '.')}/index.js`
}
],
external,
plugins: [ts_plugin]
},
...fs.readdirSync('src/runtime')
.filter(dir => fs.statSync(`src/runtime/${dir}`).isDirectory())
.map(dir => ({

@ -57,6 +57,8 @@ In development mode (see the [compiler options](docs#svelte_compile)), a warning
If you export a `const`, `class` or `function`, it is readonly from outside the component. Function *expressions* are valid props, however.
Readonly props can be accessed as properties on the element, tied to the component using [`bind:this` syntax](docs#bind_element).
```sv
<script>
// these are readonly

@ -1236,6 +1236,74 @@ As with DOM events, if the `on:` directive is used without a value, the componen
<SomeComponent on:whatever/>
```
#### [--style-props](style_props)
```sv
--style-props="anycssvalue"
```
---
As of [Svelte 3.38](https://github.com/sveltejs/svelte/issues/6268) ([RFC](https://github.com/sveltejs/rfcs/pull/13)), you can pass styles as props to components for the purposes of theming, using CSS custom properties.
Svelte's implementation is essentially syntactic sugar for adding a wrapper element. This example:
```sv
<Slider
bind:value
min={0}
--rail-color="black"
--track-color="rgb(0, 0, 255)"
/>
```
---
Desugars to this:
```sv
<div style="display: contents; --rail-color: black; --track-color: rgb(0, 0, 255)">
<Slider
bind:value
min={0}
max={100}
/>
</div>
```
**Note**: Since this is an extra div, beware that your CSS structure might accidentally target this. Be mindful of this added wrapper element when using this feature. Also note that not all browsers support `display: contents`: https://caniuse.com/css-display-contents
---
Svelte's CSS Variables support allows for easily themable components:
```sv
<!-- Slider.svelte -->
<style>
.potato-slider-rail {
background-color: var(--rail-color, var(--theme-color, 'purple'));
}
</style>
```
---
So you can set a high level theme color:
```css
/* global.css */
html {
--theme-color: black;
}
```
---
Or override it at the consumer level:
```sv
<Slider --rail-color="goldenrod"/>
```
#### [bind:*property*](bind_component_property)

@ -294,6 +294,8 @@ const unsubscribe = count.subscribe(value => {
unsubscribe(); // logs 'no more subscribers'
```
Note that the value of a `writable` is lost when it is destroyed, for example when the page is refreshed. However, you can write your own logic to sync the value to for example the `localStorage`.
#### `readable`
```js
@ -752,7 +754,7 @@ Animates the stroke of an SVG element, like a snake in a tube. `in` transitions
* `duration` (`number` | `function`, default 800) — milliseconds the transition lasts
* `easing` (`function`, default `cubicInOut`) — an [easing function](docs#svelte_easing)
The `speed` parameter is a means of setting the duration of the transition relative to the path's length. It is modifier that is applied to the length of the path: `duration = length / speed`. A path that is 1000 pixels with a speed of 1 will have a duration of `1000ms`, setting the speed to `0.5` will double that duration and setting it to `2` will halve it.
The `speed` parameter is a means of setting the duration of the transition relative to the path's length. It is a modifier that is applied to the length of the path: `duration = length / speed`. A path that is 1000 pixels with a speed of 1 will have a duration of `1000ms`, setting the speed to `0.5` will double that duration and setting it to `2` will halve it.
```sv
<script>
@ -775,8 +777,36 @@ The `speed` parameter is a means of setting the duration of the transition relat
```
<!-- Crossfade is coming soon... -->
#### `crossfade`
The `crossfade` function creates a pair of [transitions](docs#transition_fn) called `send` and `receive`. When an element is 'sent', it looks for a corresponding element being 'received', and generates a transition that transforms the element to its counterpart's position and fades it out. When an element is 'received', the reverse happens. If there is no counterpart, the `fallback` transition is used.
---
`crossfade` accepts the following parameters:
* `delay` (`number`, default 0) — milliseconds before starting
* `duration` (`number` | `function`, default 800) — milliseconds the transition lasts
* `easing` (`function`, default `cubicOut`) — an [easing function](docs#svelte_easing)
* `fallback` (`function`) — A fallback [transition](docs#transition_fn) to use for send when there is no matching element being received, and for receive when there is no element being sent.
```sv
<script>
import { crossfade } from 'svelte/transition';
import { quintOut } from 'svelte/easing';
const [send, receive] = crossfade({
duration:1500,
easing: quintOut
});
</script>
{#if condition}
<h1 in:send={{key}} out:receive={{key}}>BIG ELEM</h1>
{:else}
<small in:send={{key}} out:receive={{key}}>small elem</small>
{/if}
```
### `svelte/animate`

@ -44,7 +44,8 @@ The following options can be passed to the compiler. None are required:
| `filename` | string | `null`
| `name` | string | `"Component"`
| `format` | `"esm"` or `"cjs"` | `"esm"`
| `generate` | `"dom"` or `"ssr"` | `"dom"`
| `generate` | `"dom"` or `"ssr" or false` | `"dom"`
| `varsReport` | `"strict"` or `"full" or false` | `"strict"`
| `dev` | boolean | `false`
| `immutable` | boolean | `false`
| `hydratable` | boolean | `false`
@ -66,6 +67,7 @@ The following options can be passed to the compiler. None are required:
| `name` | `"Component"` | `string` that sets the name of the resulting JavaScript class (though the compiler will rename it if it would otherwise conflict with other variables in scope). It will normally be inferred from `filename`.
| `format` | `"esm"` | If `"esm"`, creates a JavaScript module (with `import` and `export`). If `"cjs"`, creates a CommonJS module (with `require` and `module.exports`), which is useful in some server-side rendering situations or for testing.
| `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.
| `varsReport` | `"strict"` | If `"strict"`, Svelte returns a variables report with only variables that are not globals nor internals. If `"full"`, Svelte returns a variables report with all detected variables. If `false`, no variables report is returned.
| `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` 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.

@ -191,25 +191,6 @@ Enforce that heading elements (`h1`, `h2`, etc.) and anchors have content and th
---
### `a11y-no-onchange`
Enforce usage of `on:blur` over/in parallel with `on:change` on select menu elements for accessibility. `on:blur` should be used instead of `on:change`, unless absolutely necessary and it causes no negative consequences for keyboard only or screen reader users. `on:blur` is a more declarative action by the user: for instance in a dropdown, using the arrow keys to toggle between options will trigger the `on:change` event in some browsers. Regardless, when a change of context results from an `on:blur` event or an `on:change` event, the user should be notified of the change unless it occurs below the currently focused element.
```sv
<select on:change={e => {}} on:blur={e => {}}>
<option>foo</option>
<option>bar</option>
</select>
<!-- A11y: on:blur must be used instead of on:change, unless absolutely necessary and it causes no negative consequences for keyboard only or screen reader users. -->
<select on:change={e => {}}>
<option>foo</option>
<option>bar</option>
</select>
```
---
### `a11y-positive-tabindex`
Avoid positive `tabIndex` property values to synchronize the flow of the page with keyboard tab order.

@ -5,7 +5,6 @@
import flash from './flash.js';
export let todo;
export let toggle;
let div;

@ -3,7 +3,6 @@
import flash from './flash.js';
export let todo;
export let toggle;
let div;

@ -12,7 +12,7 @@ const unsubscribe = count.subscribe(value => {
});
```
You now declared `unsubscribe`, but it still needs be to called, for example through the `onDestroy` [lifecycle hook](tutorial/ondestroy):
You now declared `unsubscribe`, but it still needs to be called, for example through the `onDestroy` [lifecycle hook](tutorial/ondestroy):
```html
<script>

@ -1711,17 +1711,6 @@
}
}
},
"clipboard": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.6.tgz",
"integrity": "sha512-g5zbiixBRk/wyKakSwCKd7vQXDjFnAMGHoEyBogG/bw9kTD9GvdAvaoRR1ALcEzt3pVKxZR0pViekPMIS0QyGg==",
"optional": true,
"requires": {
"good-listener": "^1.2.2",
"select": "^1.1.2",
"tiny-emitter": "^2.0.0"
}
},
"cliui": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
@ -1919,12 +1908,6 @@
"integrity": "sha512-nxQr1Ep4NSGDIqQ3HSMNgnPYaxPfPhHdrpgSNO2EczO86zN7NJJ1i/59GM25vgC45ANQUazZ/3Z+iyeZCmGwhg==",
"dev": true
},
"delegate": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz",
"integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==",
"optional": true
},
"devalue": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/devalue/-/devalue-2.0.1.tgz",
@ -2156,15 +2139,6 @@
"integrity": "sha512-YSwLaGMOgSBx9roJlNLL12c+FRiw7VECphinc6mGucphc/ZxTHgdEz6gmJqH6NOzYEd/yr64hwjom5pZ+tJVpg==",
"dev": true
},
"good-listener": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
"integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=",
"optional": true,
"requires": {
"delegate": "^3.1.2"
}
},
"graceful-fs": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
@ -3211,12 +3185,9 @@
"integrity": "sha512-yABh19CYbM24V7aS7TuPYRNMqthxwbvx6FF/Rw920YbyBWO3tnyPIqRMgHuSVsLmuHkkBS1Akyof463FVdkeDQ=="
},
"prismjs": {
"version": "1.23.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.23.0.tgz",
"integrity": "sha512-c29LVsqOaLbBHuIbsTxaKENh1N2EQBOHaWv7gkHN4dgRbxSREqDnDbtFJYdpPauS4YCplMSNCABQ6Eeor69bAA==",
"requires": {
"clipboard": "^2.0.0"
}
"version": "1.24.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.24.0.tgz",
"integrity": "sha512-SqV5GRsNqnzCL8k5dfAjCNhUrF3pR0A9lTDSCUZeh/LIshheXJEaP0hwLz2t4XHivd2J/v2HR+gRnigzeKe3cQ=="
},
"process": {
"version": "0.11.10",
@ -3450,12 +3421,6 @@
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
"dev": true
},
"select": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
"integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=",
"optional": true
},
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
@ -3722,12 +3687,6 @@
"integrity": "sha512-IjZc9KIotudix8bMaBW6QvMuq64BrJWFs1+4V0lXwWGQZwH+LnX87doAYhem4caOEusRP9/g6jVDQmZ8XOk1nw==",
"dev": true
},
"tiny-emitter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
"integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==",
"optional": true
},
"tinycolor2": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.2.tgz",

@ -25,7 +25,7 @@
"pg": "^7.12.1",
"polka": "^1.0.0-next.9",
"prism-svelte": "^0.4.3",
"prismjs": "^1.21.0",
"prismjs": "^1.24.0",
"sirv": "^1.0.0",
"yootils": "0.0.16"
},

@ -38,7 +38,7 @@
{#if $page.path !== '/repl/embed'}
<Nav {segment} {page} logo="svelte-logo-horizontal.svg">
<NavItem segment="tutorial">Tutorial</NavItem>
<NavItem segment="docs">API</NavItem>
<NavItem segment="docs">Docs</NavItem>
<NavItem segment="examples">Examples</NavItem>
<NavItem segment="repl">REPL</NavItem>
<NavItem segment="blog">Blog</NavItem>

@ -77,7 +77,6 @@
{selected.chapter.title}
</span>
<!-- svelte-ignore a11y-no-onchange -->
<select value={slug} on:change={navigate}>
{#each sections as section, i}
<optgroup label="{i + 1}. {section.title}">

@ -10,6 +10,7 @@ import {
Scope,
extract_identifiers
} from './utils/scope';
import { Node as PeriscopicNode } from 'periscopic';
import Stylesheet from './css/Stylesheet';
import { test } from '../config';
import Fragment from './nodes/Fragment';
@ -183,10 +184,13 @@ export default class Component {
this.stylesheet.warn_on_unused_selectors(this);
}
add_var(variable: Var) {
add_var(variable: Var, add_to_lookup = true) {
this.vars.push(variable);
if (add_to_lookup) {
this.var_lookup.set(variable.name, variable);
}
}
add_reference(name: string) {
const variable = this.var_lookup.get(name);
@ -216,6 +220,10 @@ export default class Component {
variable.subscribable = true;
}
} else {
if (this.compile_options.varsReport === 'full') {
this.add_var({ name, referenced: true }, false);
}
this.used_names.add(name);
}
}
@ -340,19 +348,7 @@ export default class Component {
css,
ast: this.original_ast,
warnings: this.warnings,
vars: this.vars
.filter(v => !v.global && !v.internal)
.map(v => ({
name: v.name,
export_name: v.export_name || null,
injected: v.injected || false,
module: v.module || false,
mutated: v.mutated || false,
reassigned: v.reassigned || false,
referenced: v.referenced || false,
writable: v.writable || false,
referenced_from_script: v.referenced_from_script || false
})),
vars: this.get_vars_report(),
stats: this.stats.render()
};
}
@ -402,6 +398,28 @@ export default class Component {
};
}
get_vars_report(): Var[] {
const { compile_options, vars } = this;
const vars_report = compile_options.varsReport === false
? []
: compile_options.varsReport === 'full'
? vars
: vars.filter(v => !v.global && !v.internal);
return vars_report.map(v => ({
name: v.name,
export_name: v.export_name || null,
injected: v.injected || false,
module: v.module || false,
mutated: v.mutated || false,
reassigned: v.reassigned || false,
referenced: v.referenced || false,
writable: v.writable || false,
referenced_from_script: v.referenced_from_script || false
}));
}
error(
pos: {
start: number;
@ -812,7 +830,7 @@ export default class Component {
if (node.type === 'AssignmentExpression' || node.type === 'UpdateExpression') {
const assignee = node.type === 'AssignmentExpression' ? node.left : node.argument;
const names = extract_names(assignee);
const names = extract_names(assignee as PeriscopicNode);
const deep = assignee.type === 'MemberExpression';

@ -147,7 +147,7 @@ export default class Selector {
for (const block of this.blocks) {
for (const selector of block.selectors) {
if (selector.type === 'PseudoClassSelector' && selector.name === 'global') {
if (/[^\\],/.test(selector.children[0].value)) {
if (/[^\\],(?!([^([]+[^\\]|[^([\\])[)\]])/.test(selector.children[0].value)) {
component.error(selector, {
code: 'css-invalid-global-selector',
message: ':global(...) must contain a single selector'
@ -610,7 +610,7 @@ class Block {
this.selectors.length >= 1 &&
this.selectors[0].type === 'PseudoClassSelector' &&
this.selectors[0].name === 'global' &&
this.selectors.every((selector) => selector.type === 'PseudoClassSelector')
this.selectors.every((selector) => selector.type === 'PseudoClassSelector' || selector.type === 'PseudoElementSelector')
);
}
}

@ -14,6 +14,7 @@ const valid_options = [
'filename',
'sourcemap',
'generate',
'varsReport',
'outputFilename',
'cssOutputFilename',
'sveltePath',

@ -57,11 +57,6 @@ const a11y_required_content = new Set([
'h6'
]);
const a11y_no_onchange = new Set([
'select',
'option'
]);
const a11y_labelable = new Set([
'button',
'input',
@ -82,7 +77,8 @@ const valid_modifiers = new Set([
'once',
'passive',
'nonpassive',
'self'
'self',
'trusted'
]);
const passive_events = new Set([
@ -542,15 +538,6 @@ export default class Element extends Node {
}
}
if (a11y_no_onchange.has(this.name)) {
if (handlers_map.has('change') && !handlers_map.has('blur')) {
component.warn(this, {
code: 'a11y-no-onchange',
message: 'A11y: on:blur must be used instead of on:change, unless absolutely necessary and it causes no negative consequences for keyboard only or screen reader users.'
});
}
}
if (a11y_distracting_elements.has(this.name)) {
// no-distracting-elements
component.warn(this, {
@ -598,6 +585,20 @@ export default class Element extends Node {
});
}
}
if (handlers_map.has('mouseover') && !handlers_map.has('focus')) {
component.warn(this, {
code: 'a11y-mouse-events-have-key-events',
message: 'A11y: on:mouseover must be accompanied by on:focus'
});
}
if (handlers_map.has('mouseout') && !handlers_map.has('blur')) {
component.warn(this, {
code: 'a11y-mouse-events-have-key-events',
message: 'A11y: on:mouseout must be accompanied by on:blur'
});
}
}
validate_bindings_foreign() {

@ -17,6 +17,7 @@ import replace_object from '../../utils/replace_object';
import is_contextual from './is_contextual';
import EachBlock from '../EachBlock';
import { clone } from '../../../utils/clone';
import { Node as PeriscopicNode } from 'periscopic';
type Owner = INode;
@ -356,7 +357,7 @@ export default class Expression {
// (a or b). In destructuring cases (`[d, e] = [e, d]`) there
// may be more, in which case we need to tack the extra ones
// onto the initial function call
const names = new Set(extract_names(assignee));
const names = new Set(extract_names(assignee as PeriscopicNode));
const traced: Set<string> = new Set();
names.forEach(name => {

@ -9,6 +9,7 @@ import Block from './Block';
import { ClassDeclaration, FunctionExpression, Node, Statement, ObjectExpression, Expression } from 'estree';
import { apply_preprocessor_sourcemap } from '../../utils/mapped_code';
import { RawSourceMap, DecodedSourceMap } from '@ampproject/remapping/dist/types/types';
import { Node as PeriscopicNode } from 'periscopic';
export default function dom(
component: Component,
@ -252,7 +253,7 @@ export default function dom(
// (a or b). In destructuring cases (`[d, e] = [e, d]`) there
// may be more, in which case we need to tack the extra ones
// onto the initial function call
const names = new Set(extract_names(assignee));
const names = new Set(extract_names(assignee as PeriscopicNode));
this.replace(invalidate(renderer, scope, node, names, execution_context === null));
}
@ -399,7 +400,7 @@ export default function dom(
unknown_props_check = b`
const writable_props = [${writable_props.map(prop => x`'${prop.export_name}'`)}];
@_Object.keys($$props).forEach(key => {
if (!~writable_props.indexOf(key) && key.slice(0, 2) !== '$$') @_console.warn(\`<${component.tag}> was created with unknown prop '\${key}'\`);
if (!~writable_props.indexOf(key) && key.slice(0, 2) !== '$$' && key !== 'slot') @_console.warn(\`<${component.tag}> was created with unknown prop '\${key}'\`);
});
`;
}

@ -42,6 +42,7 @@ export default class EventHandlerWrapper {
if (this.node.modifiers.has('preventDefault')) snippet = x`@prevent_default(${snippet})`;
if (this.node.modifiers.has('stopPropagation')) snippet = x`@stop_propagation(${snippet})`;
if (this.node.modifiers.has('self')) snippet = x`@self(${snippet})`;
if (this.node.modifiers.has('trusted')) snippet = x`@trusted(${snippet})`;
const args = [];

@ -1,10 +1,8 @@
import Renderer, { RenderOptions } from '../Renderer';
import Comment from '../../nodes/Comment';
export default function(_node: Comment, _renderer: Renderer, _options: RenderOptions) {
// TODO preserve comments
// if (options.preserveComments) {
// renderer.append(`<!--${node.data}-->`);
// }
export default function(node: Comment, renderer: Renderer, options: RenderOptions) {
if (options.preserveComments) {
renderer.add_string(`<!--${node.data}-->`);
}
}

@ -6,8 +6,9 @@ import Renderer from './Renderer';
import { INode as TemplateNode } from '../nodes/interfaces'; // TODO
import Text from '../nodes/Text';
import { LabeledStatement, Statement, Node } from 'estree';
import { Node as PeriscopicNode , extract_names } from 'periscopic';
import { walk } from 'estree-walker';
import { extract_names } from 'periscopic';
import { invalidate } from '../render_dom/invalidate';
export default function ssr(
@ -89,7 +90,7 @@ export default function ssr(
if (node.type === 'AssignmentExpression' || node.type === 'UpdateExpression') {
const assignee = node.type === 'AssignmentExpression' ? node.left : node.argument;
const names = new Set(extract_names(assignee));
const names = new Set(extract_names(assignee as PeriscopicNode));
const to_invalidate = new Set<string>();
for (const name of names) {

@ -1,8 +1,8 @@
import { Node } from 'estree';
import { analyze, Scope, extract_names, extract_identifiers } from 'periscopic';
import { Node as PeriscopicNode, analyze, Scope, extract_names, extract_identifiers } from 'periscopic';
export function create_scopes(expression: Node) {
return analyze(expression);
return analyze(expression as PeriscopicNode);
}
export { Scope, extract_names, extract_identifiers };

@ -116,6 +116,7 @@ export interface CompileOptions {
name?: string;
filename?: string;
generate?: 'dom' | 'ssr' | false;
varsReport?: 'full' | 'strict' | false;
sourcemap?: object | string;
outputFilename?: string;

@ -385,6 +385,13 @@ function read_attribute(parser: Parser, unique_names: Set<string>) {
if (type) {
const [directive_name, ...modifiers] = name.slice(colon_index + 1).split('|');
if (directive_name === '') {
parser.error({
code: 'empty-directive-name',
message: `${type} name cannot be empty`
}, start + colon_index + 1);
}
if (type === 'Binding' && directive_name !== 'this') {
check_unique(directive_name);
} else if (type !== 'EventHandler' && type !== 'Action') {
@ -398,13 +405,6 @@ function read_attribute(parser: Parser, unique_names: Set<string>) {
}, start);
}
if (type === 'Class' && directive_name === '') {
parser.error({
code: 'invalid-class-directive',
message: 'Class binding name cannot be empty'
}, start + colon_index + 1);
}
if (value[0]) {
if ((value as any[]).length > 1 || value[0].type === 'Text') {
parser.error({

@ -4,7 +4,10 @@ import { blank_object, is_empty, is_function, run, run_all, noop } from './utils
import { children, detach, start_hydrating, end_hydrating } from './dom';
import { transition_in } from './transitions';
interface Fragment {
/**
* INTERNAL, DO NOT USE. Code may change at any time.
*/
export interface Fragment {
key: string|null;
first: null;
/* create */ c: () => void;

@ -214,6 +214,13 @@ export function self(fn) {
};
}
export function trusted(fn) {
return function(event) {
// @ts-ignore
if (event.isTrusted) fn.call(this, event);
};
}
export function attr(node: Element, attribute: string, value?: string) {
if (value == null) node.removeAttribute(attribute);
else if (node.getAttribute(attribute) !== value) node.setAttribute(attribute, value);

@ -5,8 +5,25 @@ import { create_rule, delete_rule } from './style_manager';
import { custom_event } from './dom';
import { add_render_callback } from './scheduler';
import { TransitionConfig } from '../transition';
import { Fragment } from './Component';
let promise: Promise<void>|null;
type INTRO = 1;
type OUTRO = 0;
interface Outro {
/**
* remaining outros
*/
r: number;
/**
* callbacks
*/
c: Function[];
/**
* parent outro
*/
p: Outro;
}
function wait() {
if (!promise) {
@ -19,12 +36,12 @@ function wait() {
return promise;
}
function dispatch(node: Element, direction: boolean, kind: 'start' | 'end') {
function dispatch(node: Element, direction: INTRO | OUTRO | boolean, kind: 'start' | 'end') {
node.dispatchEvent(custom_event(`${direction ? 'intro' : 'outro'}${kind}`));
}
const outroing = new Set();
let outros;
let outros: Outro;
export function group_outros() {
outros = {
@ -41,14 +58,14 @@ export function check_outros() {
outros = outros.p;
}
export function transition_in(block, local?: 0 | 1) {
export function transition_in(block: Fragment, local?: 0 | 1) {
if (block && block.i) {
outroing.delete(block);
block.i(local);
}
}
export function transition_out(block, local: 0 | 1, detach?: 0 | 1, callback?) {
export function transition_out(block: Fragment, local: 0 | 1, detach?: 0 | 1, callback?) {
if (block && block.o) {
if (outroing.has(block)) return;
outroing.add(block);
@ -225,21 +242,39 @@ export function create_out_transition(node: Element & ElementCSSInlineStyle, fn:
};
}
interface PendingProgram {
start: number;
b: INTRO | OUTRO;
group?: Outro;
}
interface Program {
a: number;
b: INTRO | OUTRO;
/**
* direction
*/
d: 1 | -1;
duration: number;
start: number;
end: number;
group?: Outro;
}
export function create_bidirectional_transition(node: Element & ElementCSSInlineStyle, fn: TransitionFn, params: any, intro: boolean) {
let config = fn(node, params);
let t = intro ? 0 : 1;
let running_program = null;
let pending_program = null;
let running_program: Program | null = null;
let pending_program: PendingProgram | null = null;
let animation_name = null;
function clear_animation() {
if (animation_name) delete_rule(node, animation_name);
}
function init(program, duration) {
const d = program.b - t;
function init(program: PendingProgram, duration: number): Program {
const d = (program.b - t) as Program['d'];
duration *= Math.abs(d);
return {
@ -253,7 +288,7 @@ export function create_bidirectional_transition(node: Element & ElementCSSInline
};
}
function go(b) {
function go(b: INTRO | OUTRO) {
const {
delay = 0,
duration = 300,
@ -262,7 +297,7 @@ export function create_bidirectional_transition(node: Element & ElementCSSInline
css
} = config || null_transition;
const program = {
const program: PendingProgram = {
start: now() + delay,
b
};
@ -331,7 +366,7 @@ export function create_bidirectional_transition(node: Element & ElementCSSInline
}
return {
run(b) {
run(b: INTRO | OUTRO) {
if (is_function(config)) {
wait().then(() => {
// @ts-ignore

@ -28,7 +28,7 @@ export function blank_object() {
return Object.create(null);
}
export function run_all(fns) {
export function run_all(fns: Function[]) {
fns.forEach(run);
}

@ -0,0 +1,14 @@
export {
setContext,
getContext,
hasContext,
tick,
createEventDispatcher,
SvelteComponent,
SvelteComponentTyped
} from './index';
export function onMount() {}
export function onDestroy() {}
export function beforeUpdate() {}
export function afterUpdate() {}

@ -4,7 +4,7 @@ import glob from 'tiny-glob/sync';
import * as path from 'path';
import * as fs from 'fs';
import * as colors from 'kleur';
export const assert = (assert$1 as unknown) as typeof assert$1 & { htmlEqual: (actual, expected, message?) => void };
export const assert = (assert$1 as unknown) as typeof assert$1 & { htmlEqual: (actual, expected, message?) => void, htmlEqualWithComments: (actual, expected, message?) => void };
// for coverage purposes, we need to test source files,
// but for sanity purposes, we need to test dist files
@ -118,6 +118,9 @@ function cleanChildren(node) {
node.removeChild(child);
child = previous;
}
} else if (child.nodeType === 8) {
// comment
// do nothing
} else {
cleanChildren(child);
}
@ -137,11 +140,11 @@ function cleanChildren(node) {
}
}
export function normalizeHtml(window, html) {
export function normalizeHtml(window, html, preserveComments = false) {
try {
const node = window.document.createElement('div');
node.innerHTML = html
.replace(/<!--.*?-->/g, '')
.replace(/(<!--.*?-->)/g, preserveComments ? '$1' : '')
.replace(/>[\s\r\n]+</g, '><')
.trim();
cleanChildren(node);
@ -162,6 +165,14 @@ export function setupHtmlEqual() {
message
);
};
// eslint-disable-next-line no-import-assign
assert.htmlEqualWithComments = (actual, expected, message) => {
assert.deepEqual(
normalizeHtml(window, actual, true),
normalizeHtml(window, expected, true),
message
);
};
}
export function loadConfig(file) {

@ -116,7 +116,7 @@ function instance($$self, $$props, $$invalidate) {
const writable_props = ["prop", "alias"];
Object.keys($$props).forEach(key => {
if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$") console.warn(`<Component> was created with unknown prop '${key}'`);
if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$" && key !== "slot") console.warn(`<Component> was created with unknown prop '${key}'`);
});
$$self.$$set = $$props => {

@ -75,7 +75,7 @@ function instance($$self, $$props, $$invalidate) {
const writable_props = ["name"];
Object.keys($$props).forEach(key => {
if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$") console.warn(`<Component> was created with unknown prop '${key}'`);
if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$" && key !== "slot") console.warn(`<Component> was created with unknown prop '${key}'`);
});
$$self.$$set = $$props => {

@ -179,7 +179,7 @@ function instance($$self, $$props, $$invalidate) {
const writable_props = ["things", "foo", "bar", "baz"];
Object.keys($$props).forEach(key => {
if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$") console.warn(`<Component> was created with unknown prop '${key}'`);
if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$" && key !== "slot") console.warn(`<Component> was created with unknown prop '${key}'`);
});
$$self.$$set = $$props => {

@ -171,7 +171,7 @@ function instance($$self, $$props, $$invalidate) {
const writable_props = ["things", "foo"];
Object.keys($$props).forEach(key => {
if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$") console.warn(`<Component> was created with unknown prop '${key}'`);
if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$" && key !== "slot") console.warn(`<Component> was created with unknown prop '${key}'`);
});
$$self.$$set = $$props => {

@ -56,7 +56,7 @@ function instance($$self, $$props, $$invalidate) {
const writable_props = [];
Object.keys($$props).forEach(key => {
if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$") console.warn(`<Component> was created with unknown prop '${key}'`);
if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$" && key !== "slot") console.warn(`<Component> was created with unknown prop '${key}'`);
});
$$self.$capture_state = () => ({ obj, kobzol });

@ -141,7 +141,7 @@ function instance($$self, $$props) {
const writable_props = [];
Object.keys($$props).forEach(key => {
if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$") console.warn(`<Component> was created with unknown prop '${key}'`);
if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$" && key !== "slot") console.warn(`<Component> was created with unknown prop '${key}'`);
});
return [];

@ -72,7 +72,7 @@ function instance($$self, $$props, $$invalidate) {
const writable_props = ["foo"];
Object.keys($$props).forEach(key => {
if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$") console.warn(`<Component> was created with unknown prop '${key}'`);
if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$" && key !== "slot") console.warn(`<Component> was created with unknown prop '${key}'`);
});
$$self.$$set = $$props => {

@ -110,7 +110,7 @@ function instance($$self, $$props, $$invalidate) {
const writable_props = [];
Object.keys($$props).forEach(key => {
if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$") console_1.warn(`<Component> was created with unknown prop '${key}'`);
if (!~writable_props.indexOf(key) && key.slice(0, 2) !== "$$" && key !== "slot") console_1.warn(`<Component> was created with unknown prop '${key}'`);
});
function div_binding($$value) {

@ -3,7 +3,7 @@ import { create_ssr_component } from "svelte/internal";
const Component = create_ssr_component(($$result, $$props, $$bindings, slots) => {
return `<div>content</div>
<!-- comment -->
<div>more content</div>`;
});

@ -1,6 +1,6 @@
{
"code": "invalid-class-directive",
"message": "Class binding name cannot be empty",
"code": "empty-directive-name",
"message": "Class name cannot be empty",
"start": {
"line": 1,
"column": 10,

@ -0,0 +1,10 @@
{
"code": "empty-directive-name",
"message": "Action name cannot be empty",
"start": {
"line": 1,
"column": 8,
"character": 8
},
"pos": 8
}

@ -0,0 +1,9 @@
export default {
async test({ assert, component, target, window }) {
const button = target.querySelector('button');
const event = new window.MouseEvent('click');
await button.dispatchEvent(event);
assert.equal(component.trusted, true);
}
};

@ -0,0 +1,5 @@
<script>
export let trusted = true;
</script>
<button on:click|trusted="{() => trusted = false}">Only trusted events: {trusted?'true':'false'}</button>

@ -81,7 +81,9 @@ describe('ssr', () => {
if (css.code) fs.writeFileSync(`${dir}/_actual.css`, css.code);
try {
assert.htmlEqual(html, expectedHtml);
(compileOptions.preserveComments
? assert.htmlEqualWithComments
: assert.htmlEqual)(html, expectedHtml);
} catch (error) {
if (shouldUpdateExpected()) {
fs.writeFileSync(`${dir}/_expected.html`, html);

@ -0,0 +1,5 @@
export default {
compileOptions: {
preserveComments: true
}
};

@ -0,0 +1,3 @@
<p>before</p>
<!-- a comment -->
<p>after</p>

@ -0,0 +1,3 @@
<p>before</p>
<!-- a comment -->
<p>after</p>

@ -0,0 +1,15 @@
<script>
// Even if otherProps contains onBlur and/or onFocus, the rule will still fail.
// Props should be passed down explicitly for rule to pass.
const otherProps = {
onBlur: () => void 0,
onFocus: () => void 0,
};
</script>
<div on:mouseover={() => void 0} />
<div on:mouseover={() => void 0} on:focus={() => void 0} />
<div on:mouseover={() => void 0} {...otherProps} />
<div on:mouseout={() => void 0} />
<div on:mouseout={() => void 0} on:blur={() => void 0} />
<div on:mouseout={() => void 0} {...otherProps} />

@ -0,0 +1,62 @@
[
{
"code": "a11y-mouse-events-have-key-events",
"end": {
"character": 272,
"column": 35,
"line": 10
},
"message": "A11y: on:mouseover must be accompanied by on:focus",
"pos": 237,
"start": {
"character": 237,
"column": 0,
"line": 10
}
},
{
"code": "a11y-mouse-events-have-key-events",
"end": {
"character": 384,
"column": 51,
"line": 12
},
"message": "A11y: on:mouseover must be accompanied by on:focus",
"pos": 333,
"start": {
"character": 333,
"column": 0,
"line": 12
}
},
{
"code": "a11y-mouse-events-have-key-events",
"end": {
"character": 419,
"column": 34,
"line": 13
},
"message": "A11y: on:mouseout must be accompanied by on:blur",
"pos": 385,
"start": {
"character": 385,
"column": 0,
"line": 13
}
},
{
"code": "a11y-mouse-events-have-key-events",
"end": {
"character": 528,
"column": 50,
"line": 15
},
"message": "A11y: on:mouseout must be accompanied by on:blur",
"pos": 478,
"start": {
"character": 478,
"column": 0,
"line": 15
}
}
]

@ -1,16 +0,0 @@
<select on:change={e => {}}>
<option>foo</option>
<option>bar</option>
</select>
<select on:change={e => {}} on:blur={e => {}}>
<option>foo</option>
<option>bar</option>
</select>
<select>
<option on:change={e => {}}>foo</option>
<option>bar</option>
</select>
<select>
<option on:change={e => {}} on:blur={e => {}}>foo</option>
<option>bar</option>
</select>

@ -1,32 +0,0 @@
[
{
"code": "a11y-no-onchange",
"end": {
"character": 88,
"column": 9,
"line": 4
},
"message": "A11y: on:blur must be used instead of on:change, unless absolutely necessary and it causes no negative consequences for keyboard only or screen reader users.",
"pos": 0,
"start": {
"character": 0,
"column": 0,
"line": 1
}
},
{
"code": "a11y-no-onchange",
"end": {
"character": 249,
"column": 44,
"line": 10
},
"message": "A11y: on:blur must be used instead of on:change, unless absolutely necessary and it causes no negative consequences for keyboard only or screen reader users.",
"pos": 209,
"start": {
"character": 209,
"column": 4,
"line": 10
}
}
]

@ -0,0 +1,15 @@
[{
"code": "css-invalid-global",
"message": ":global(...) can be at the start or end of a selector sequence, but not in the middle",
"start": {
"line": 5,
"column": 6,
"character": 60
},
"end": {
"line": 5,
"column": 19,
"character": 73
},
"pos": 60
}]

@ -0,0 +1,8 @@
<style>
.foo :global(.bar)::after {
color: red;
}
.foo :global(.bar)::after .baz {
color: red;
}
</style>

@ -0,0 +1,17 @@
[
{
"code": "css-invalid-global-selector",
"message": ":global(...) must contain a single selector",
"start": {
"line": 11,
"column": 5,
"character": 156
},
"end": {
"line": 11,
"column": 29,
"character": 180
},
"pos": 156
}
]

@ -0,0 +1,18 @@
<style>
div :global(:is(h1, h2)) {
color: red;
}
div :global(:where(h1, h2)) {
color: red;
}
div :global(h1 ~ :is(h2, h3)) {
color: red;
}
div :global(:is(h1, h2), h3) {
color: red;
}
</style>
<div>
<h1>hello world</h1>
</div>

@ -0,0 +1,17 @@
[
{
"code": "css-invalid-global-selector",
"message": ":global(...) must contain a single selector",
"start": {
"line": 5,
"column": 5,
"character": 77
},
"end": {
"line": 5,
"column": 44,
"character": 116
},
"pos": 77
}
]

@ -0,0 +1,12 @@
<style>
div :global(h1[data-title="Hello, world!"]) {
color: red;
}
div :global(h1[attribute], video[autoplay]) {
color: red;
}
</style>
<div>
<h1>hello world</h1>
</div>

@ -0,0 +1,17 @@
[
{
"code": "css-invalid-global-selector",
"message": ":global(...) must contain a single selector",
"start": {
"line": 2,
"column": 5,
"character": 13
},
"end": {
"line": 2,
"column": 24,
"character": 32
},
"pos": 13
}
]

@ -0,0 +1,9 @@
<style>
div :global(h1, .abc\)) {
color: red;
}
</style>
<div>
<h1>hello world</h1>
</div>

@ -0,0 +1,15 @@
[{
"code": "default-export",
"message": "A component cannot have a default export",
"pos": 10,
"start": {
"line": 2,
"column": 1,
"character": 10
},
"end": {
"line": 2,
"column": 30,
"character": 39
}
}]

@ -0,0 +1,3 @@
<script>
export default function () {};
</script>

@ -0,0 +1,15 @@
[{
"code": "default-export",
"message": "A component cannot have a default export",
"pos": 10,
"start": {
"line": 2,
"column": 1,
"character": 10
},
"end": {
"line": 2,
"column": 24,
"character": 33
}
}]

@ -0,0 +1,3 @@
<script>
export default class {};
</script>

@ -1,5 +1,5 @@
[{
"message": "Valid event modifiers are preventDefault, stopPropagation, capture, once, passive, nonpassive or self",
"message": "Valid event modifiers are preventDefault, stopPropagation, capture, once, passive, nonpassive, self or trusted",
"code": "invalid-event-modifier",
"start": {
"line": 1,

@ -0,0 +1,9 @@
export default {
options: {
varsReport: false
},
test(assert, vars) {
assert.deepEqual(vars, []);
}
};

@ -0,0 +1,5 @@
<script>
export let foo = "bar";
</script>
{foo}

@ -0,0 +1,19 @@
export default {
options: {
varsReport: 'full'
},
test(assert, vars) {
assert.deepEqual(vars, [{
name: 'foo',
export_name: null,
injected: false,
module: false,
mutated: false,
reassigned: false,
referenced: true,
referenced_from_script: false,
writable: false
}]);
}
};

@ -0,0 +1,31 @@
export default {
options: {
varsReport: 'full'
},
test(assert, vars) {
assert.deepEqual(vars, [
{
name: 'foo',
export_name: 'foo',
injected: false,
module: false,
mutated: false,
reassigned: false,
referenced: true,
referenced_from_script: false,
writable: true
}, {
name: 'bar',
export_name: null,
injected: false,
module: false,
mutated: false,
reassigned: false,
referenced: true,
referenced_from_script: false,
writable: false
}
]);
}
};

@ -0,0 +1,5 @@
<script>
export let foo = "bar";
</script>
{foo} {bar}

@ -0,0 +1,19 @@
export default {
options: {
varsReport: 'full'
},
test(assert, vars) {
assert.deepEqual(vars, [{
name: 'foo',
export_name: null,
injected: false,
module: false,
mutated: false,
reassigned: false,
referenced: true,
referenced_from_script: false,
writable: false
}]);
}
};
Loading…
Cancel
Save