Merge branch 'master' into issue-3544

pull/3679/head
Jesse Skinner 6 years ago committed by GitHub
commit a0285fc5f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -4,13 +4,22 @@
* Fix `{#each}` context not shadowing outer scope when using `bind:` ([#1565](https://github.com/sveltejs/svelte/issues/1565))
* Fix edge cases in matching selectors against elements ([#1710](https://github.com/sveltejs/svelte/issues/1710))
* Allow exiting a reactive block early with `break $` ([#2828](https://github.com/sveltejs/svelte/issues/2828))
* Don't lose `class:` directive classes on an element with `{...spread}` attributes when updating ([#3421](https://github.com/sveltejs/svelte/issues/3421))
* Check attributes have changed before setting them to avoid image flicker ([#3579](https://github.com/sveltejs/svelte/pull/3579))
* Fix generating malformed code for `{@debug}` tags with no dependencies ([#3588](https://github.com/sveltejs/svelte/issue/3588))
* Fix generated code in specific case involving compound ifs and child components ([#3595](https://github.com/sveltejs/svelte/issue/3595))
* Fix `bind:this` binding to a store ([#3591](https://github.com/sveltejs/svelte/issue/3591))
* Use safer `HTMLElement` check before extending class ([#3608](https://github.com/sveltejs/svelte/issue/3608))
* Add `location` as a known global ([#3619](https://github.com/sveltejs/svelte/pull/3619))
* Support `{#await}` with `{:catch}` but no `{:then}` ([#3623](https://github.com/sveltejs/svelte/issues/3623))
* Clean up dead code emitted for `<slot/>`s ([#3631](https://github.com/sveltejs/svelte/issues/3631))
* Fix tracking of dependencies of compound assignments in reactive statements ([#3634](https://github.com/sveltejs/svelte/issues/3634))
* Flush changes in newly attached block when using `{#await}` ([#3660](https://github.com/sveltejs/svelte/issues/3660))
* Throw exception immediately when calling `createEventDispatcher()` after component instantiation ([#3667](https://github.com/sveltejs/svelte/pull/3667))
* Fix globals shadowing contextual template scope ([#3674](https://github.com/sveltejs/svelte/issues/3674))
* Fix error resulting from trying to set a read-only property when spreading element attributes ([#3681](https://github.com/sveltejs/svelte/issues/3681))
* Fix handling of boolean attributes in presence of other spread attributes ([#3764](https://github.com/sveltejs/svelte/issues/3764))
## 3.12.1

53
package-lock.json generated

@ -1,6 +1,6 @@
{
"name": "svelte",
"version": "3.13.0-alpha.0",
"version": "3.13.0-alpha.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -167,9 +167,9 @@
"dev": true
},
"acorn": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.0.0.tgz",
"integrity": "sha512-PaF/MduxijYYt7unVGRuds1vBC9bFxbNf+VWqhOClfdgy7RlVkQqt610ig1/yxTgsDIfW1cWDel5EBbOy3jdtQ==",
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz",
"integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==",
"dev": true
},
"acorn-globals": {
@ -490,14 +490,14 @@
"dev": true
},
"code-red": {
"version": "0.0.17",
"resolved": "https://registry.npmjs.org/code-red/-/code-red-0.0.17.tgz",
"integrity": "sha512-RJJ48sXYOqyd0J4QelF4dRdYb+4DaLV/jHs4mNoxOdLroUGB840cMc9pMtEAbGKjFFzoTKREypFzqphBD8knMg==",
"version": "0.0.18",
"resolved": "https://registry.npmjs.org/code-red/-/code-red-0.0.18.tgz",
"integrity": "sha512-g7W6RwRqBbQTtMaUqrNWDyyl2GK0Uulk/uZPzGdgTXpOGX/LA8bW67EKQLdQgpYfd6APhZVwoX2lrL7mnJOWkA==",
"dev": true,
"requires": {
"acorn": "^7.0.0",
"is-reference": "^1.1.3",
"periscopic": "^1.0.1",
"acorn": "^7.1.0",
"is-reference": "^1.1.4",
"periscopic": "^1.0.2",
"sourcemap-codec": "^1.4.6"
}
},
@ -1147,14 +1147,6 @@
"acorn": "^7.0.0",
"acorn-jsx": "^5.0.2",
"eslint-visitor-keys": "^1.1.0"
},
"dependencies": {
"acorn": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.0.0.tgz",
"integrity": "sha512-PaF/MduxijYYt7unVGRuds1vBC9bFxbNf+VWqhOClfdgy7RlVkQqt610ig1/yxTgsDIfW1cWDel5EBbOy3jdtQ==",
"dev": true
}
}
},
"esprima": {
@ -1768,9 +1760,9 @@
"dev": true
},
"is-reference": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.1.3.tgz",
"integrity": "sha512-W1iHHv/oyBb2pPxkBxtaewxa1BC58Pn5J0hogyCdefwUIvb6R+TGbAcIa4qPNYLqLhb3EnOgUf2MQkkF76BcKw==",
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.1.4.tgz",
"integrity": "sha512-uJA/CDPO3Tao3GTrxYn6AwkM4nUPJiGGYu5+cB8qbC7WGFlrKZbiRo7SFKxUAEpFUfiHofWCXBUNhvYJMh+6zw==",
"dev": true,
"requires": {
"@types/estree": "0.0.39"
@ -2727,17 +2719,6 @@
"dev": true,
"requires": {
"is-reference": "^1.1.4"
},
"dependencies": {
"is-reference": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.1.4.tgz",
"integrity": "sha512-uJA/CDPO3Tao3GTrxYn6AwkM4nUPJiGGYu5+cB8qbC7WGFlrKZbiRo7SFKxUAEpFUfiHofWCXBUNhvYJMh+6zw==",
"dev": true,
"requires": {
"@types/estree": "0.0.39"
}
}
}
},
"pify": {
@ -3646,14 +3627,6 @@
"@types/istanbul-lib-coverage": "^2.0.1",
"convert-source-map": "^1.6.0",
"source-map": "^0.7.3"
},
"dependencies": {
"source-map": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
"dev": true
}
}
},
"validate-npm-package-license": {

@ -1,6 +1,6 @@
{
"name": "svelte",
"version": "3.13.0-alpha.0",
"version": "3.13.0-alpha.1",
"description": "Cybernetically enhanced web apps",
"module": "index.mjs",
"main": "index",
@ -60,17 +60,17 @@
"@types/node": "^8.10.53",
"@typescript-eslint/eslint-plugin": "^1.13.0",
"@typescript-eslint/parser": "^2.1.0",
"acorn": "^7.0.0",
"acorn": "^7.1.0",
"agadoo": "^1.1.0",
"c8": "^5.0.1",
"code-red": "0.0.17",
"code-red": "0.0.18",
"codecov": "^3.5.0",
"css-tree": "1.0.0-alpha22",
"eslint": "^6.3.0",
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-svelte3": "^2.7.3",
"estree-walker": "^0.8.1",
"is-reference": "^1.1.3",
"is-reference": "^1.1.4",
"jsdom": "^15.1.1",
"kleur": "^3.0.3",
"locate-character": "^2.0.5",

@ -184,7 +184,7 @@ dispatch: ((name: string, detail?: any) => void) = createEventDispatcher();
---
Creates an event dispatcher that can be used to dispatch [component events](docs#Component_events). Event dispatchers are functions that can take two arguments: `name` and `detail`.
Creates an event dispatcher that can be used to dispatch [component events](docs#on_component_event). Event dispatchers are functions that can take two arguments: `name` and `detail`.
Component events created with `createEventDispatcher` create a [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent). These events do not [bubble](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#Event_bubbling_and_capture) and are not cancellable with `event.preventDefault()`. The `detail` argument corresponds to the [CustomEvent.detail](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/detail) property and can contain any type of data.
@ -1019,8 +1019,12 @@ Unlike client-side components, server-side components don't have a lifespan afte
A server-side component exposes a `render` method that can be called with optional props. It returns an object with `head`, `html`, and `css` properties, where `head` contains the contents of any `<svelte:head>` elements encountered.
You can import a Svelte component directly into Node using [`svelte/register`](docs#svelte_register).
```js
const App = require('./App.svelte');
require('svelte/register');
const App = require('./App.svelte').default;
const { head, html, css } = App.render({
answer: 42

@ -55,6 +55,7 @@
<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>

@ -15,7 +15,7 @@ const app = polka({
});
if (process.env.PGHOST) {
app.use(authenticate);
app.use(authenticate());
}
app.use(

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="523" height="158"><rect id="backgroundrect" width="100%" height="100%" x="0" y="0" fill="none" stroke="none"/><style>.st0{fill:#fff}</style><g class="currentLayer"><path class="st0" d="M21.1 18.7v27.6h53.3V65H21.1v26.7h60.1v18.7H0V0h81.2v18.7H21.1z" id="svg_1"/><path d="M195.9 156.4H0v-18.7h195.9v18.7z" id="svg_2" fill="#ff5959"/><path class="st0" d="M193.7 110.2l-72.4-65.5v65.5h-21.1V0h2.3l72.4 66.7V0H196v110.2h-2.3z" id="svg_3"/><path class="st0" d="M299.8 64.9h-31.5v91.5h-21.1V64.9h-31.5V46.2h84.2v18.7z" id="svg_4"/><path class="st0" d="M363.1 158.4c-7.1 0-13.6-1.1-19.4-3.3-5.8-2.2-10.8-5.3-14.9-9.3-4.1-4-7.3-8.9-9.6-14.6-2.3-5.7-3.4-12.1-3.4-19.1V46.3h21.1V112c0 5.5.9 10.1 2.7 13.7 1.8 3.6 4 6.5 6.7 8.6 2.7 2.1 5.5 3.6 8.6 4.4 3.1.8 5.8 1.2 8.2 1.2 2.4 0 5.1-.4 8.2-1.2 3.1-.8 5.9-2.3 8.6-4.4 2.7-2.1 4.9-5 6.7-8.6 1.8-3.6 2.7-8.2 2.7-13.7V46.2h21.1V112c0 7-1.1 13.4-3.3 19.1-2.2 5.7-5.4 10.6-9.5 14.6s-9.1 7.1-14.9 9.3c-5.8 2.3-12.4 3.4-19.6 3.4z" id="svg_5"/><path class="st0" d="M451.2 121.4v35.1h-21.1V46.2h50.1c5.6 0 10.8.9 15.5 2.8 4.7 1.9 8.7 4.5 12 7.8 3.3 3.4 5.9 7.4 7.8 12 1.9 4.7 2.8 9.8 2.8 15.3 0 4.1-.6 8.1-1.7 12-1.1 3.9-2.6 7.4-4.6 10.5-2 3.1-4.4 5.8-7.2 8.1-2.9 2.3-6 3.8-9.4 4.7l27.4 37h-25.1l-25.5-35.1h-21zm0-18.4h24c4.4 0 8-.6 10.9-1.7 2.9-1.1 5.1-2.6 6.8-4.4 1.7-1.8 2.8-3.8 3.4-6.1.6-2.3.9-4.5.9-6.9 0-2.7-.4-5.2-1.3-7.5s-2.2-4.3-3.9-6c-1.8-1.7-4-3.1-6.8-4.1-2.8-1-6.1-1.5-10-1.5h-24V103z" id="svg_6"/></g></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@ -90,15 +90,6 @@ export default class Component {
file: string;
locate: (c: number) => { line: number; column: number };
// TODO this does the same as component.locate! remove one or the other
locator: (
search: number,
startIndex?: number
) => {
line: number;
column: number;
};
stylesheet: Stylesheet;
aliases: Map<string, Identifier> = new Map();
@ -140,7 +131,7 @@ export default class Component {
.replace(process.cwd(), '')
.replace(/^[/\\]/, '')
: compile_options.filename);
this.locate = getLocator(this.source);
this.locate = getLocator(this.source, { offsetLine: 1 });
// styles
this.stylesheet = new Stylesheet(
@ -438,12 +429,8 @@ export default class Component {
return;
}
if (!this.locator) {
this.locator = getLocator(this.source, { offsetLine: 1 });
}
const start = this.locator(pos.start);
const end = this.locator(pos.end);
const start = this.locate(pos.start);
const end = this.locate(pos.end);
const frame = get_code_frame(this.source, start.line - 1, start.column);
@ -456,7 +443,7 @@ export default class Component {
pos: pos.start,
filename: this.compile_options.filename,
toString: () =>
`${warning.message} (${start.line + 1}:${start.column})\n${frame}`,
`${warning.message} (${start.line}:${start.column})\n${frame}`,
});
}
@ -582,6 +569,7 @@ export default class Component {
this.add_var({
name,
global: true,
hoistable: true
});
}
});
@ -674,6 +662,7 @@ export default class Component {
this.add_var({
name,
global: true,
hoistable: true
});
}
});
@ -772,12 +761,12 @@ export default class Component {
invalidate(name, value?) {
const variable = this.var_lookup.get(name);
if (variable && (variable.subscribable && variable.reassigned)) {
if (variable && (variable.subscribable && (variable.reassigned || variable.export_name))) {
return x`${`$$subscribe_${name}`}($$invalidate('${name}', ${value || name}))`;
}
if (name[0] === '$' && name[1] !== '$') {
return x`${name.slice(1)}.set(${name})`;
return x`${name.slice(1)}.set(${value || name})`;
}
if (

@ -9,7 +9,7 @@ import TemplateScope from './shared/TemplateScope';
import { x } from 'code-red';
export default class Attribute extends Node {
type: 'Attribute';
type: 'Attribute' | 'Spread';
start: number;
end: number;
scope: TemplateScope;

@ -3,7 +3,7 @@ import { walk } from 'estree-walker';
import is_reference from 'is-reference';
import flatten_reference from '../../utils/flatten_reference';
import { create_scopes, Scope, extract_names } from '../../utils/scope';
import { globals, sanitize } from '../../../utils/names';
import { sanitize } from '../../../utils/names';
import Wrapper from '../../render_dom/wrappers/shared/Wrapper';
import TemplateScope from './TemplateScope';
import get_object from '../../utils/get_object';
@ -75,8 +75,6 @@ export default class Expression {
if (scope.has(name)) return;
if (globals.has(name) && !(component.var_lookup.has(name) || template_scope.names.has(name))) return;
if (name[0] === '$' && template_scope.names.has(name.slice(1))) {
component.error(node, {
code: `contextual-store`,
@ -202,7 +200,6 @@ export default class Expression {
const { name } = flatten_reference(node);
if (scope.has(name)) return;
if (globals.has(name) && !(component.var_lookup.has(name) || template_scope.names.has(name))) return;
if (function_expression) {
if (template_scope.names.has(name)) {

@ -146,11 +146,13 @@ export default class Block {
if (!wrapper.var) continue;
let suffix = '';
if (dupes.has(wrapper.var.name)) {
const i = counts.get(wrapper.var.name) || 0;
counts.set(wrapper.var.name, i + 1);
wrapper.var.name = this.get_unique_name(wrapper.var.name + i).name;
suffix = i;
}
wrapper.var.name = this.get_unique_name(wrapper.var.name + suffix).name;
}
}

@ -221,18 +221,15 @@ export default function dom(
}
});
component.rewrite_props(({ name, reassigned }) => {
component.rewrite_props(({ name, reassigned, export_name }) => {
const value = `$${name}`;
if (reassigned) {
return b`${`$$subscribe_${name}`}()`;
}
const callback = x`$$value => $$invalidate('${value}', ${value} = $$value)`;
const insert = (reassigned || export_name)
? b`${`$$subscribe_${name}`}()`
: b`@component_subscribe($$self, ${name}, #value => $$invalidate('${value}', ${value} = #value))`;
let insert = b`@component_subscribe($$self, ${name}, $${callback})`;
if (component.compile_options.dev) {
insert = b`@validate_store(${name}, '${name}'); ${insert}`;
return b`@validate_store(${name}, '${name}'); ${insert}`;
}
return insert;
@ -304,14 +301,14 @@ export default function dom(
return !variable || variable.hoistable;
})
.map(({ name }) => b`
${component.compile_options.dev && `@validate_store(${name.slice(1)}, '${name.slice(1)}');`}
${component.compile_options.dev && b`@validate_store(${name.slice(1)}, '${name.slice(1)}');`}
@component_subscribe($$self, ${name.slice(1)}, $$value => $$invalidate('${name}', ${name} = $$value));
`);
const resubscribable_reactive_store_unsubscribers = reactive_stores
.filter(store => {
const variable = component.var_lookup.get(store.name.slice(1));
return variable && variable.reassigned;
return variable && (variable.reassigned || variable.export_name);
})
.map(({ name }) => b`$$self.$$.on_destroy.push(() => ${`$$unsubscribe_${name.slice(1)}`}());`);
@ -353,7 +350,7 @@ export default function dom(
const name = $name.slice(1);
const store = component.var_lookup.get(name);
if (store && store.reassigned) {
if (store && (store.reassigned || store.export_name)) {
const unsubscribe = `$$unsubscribe_${name}`;
const subscribe = `$$subscribe_${name}`;
return b`let ${$name}, ${unsubscribe} = @noop, ${subscribe} = () => (${unsubscribe}(), ${unsubscribe} = @subscribe(${name}, $$value => $$invalidate('${$name}', ${$name} = $$value)), ${name})`;

@ -30,7 +30,6 @@ export default class DebugTagWrapper extends Wrapper {
const { var_lookup } = component;
const start = component.locate(this.node.start + 1);
start.line += 1;
const end = { line: start.line, column: start.column + 6 };
const loc = { start, end };

@ -108,7 +108,6 @@ export default class EachBlockWrapper extends Wrapper {
let c = this.node.start + 2;
while (renderer.component.source[c] !== 'e') c += 1;
const start = renderer.component.locate(c);
start.line += 1;
const end = { line: start.line, column: start.column + 4 };
const length = {
type: 'Identifier',

@ -44,9 +44,7 @@ export default class AttributeWrapper {
const element = this.parent;
const name = fix_attribute_casing(this.node.name);
let metadata = element.node.namespace ? null : attribute_lookup[name];
if (metadata && metadata.applies_to && !~metadata.applies_to.indexOf(element.node.name))
metadata = null;
const metadata = this.get_metadata();
const is_indirectly_bound_value =
name === 'value' &&
@ -95,7 +93,7 @@ export default class AttributeWrapper {
const is_select_value_attribute =
name === 'value' && element.node.name === 'select';
const should_cache = (this.node.should_cache() || is_select_value_attribute);
const should_cache = is_select_value_attribute; // TODO is this necessary?
const last = should_cache && block.get_unique_name(
`${element.var.name}_${name.replace(/[^a-zA-Z_$]/g, '_')}_value`
@ -193,6 +191,13 @@ export default class AttributeWrapper {
}
}
get_metadata() {
if (this.parent.node.namespace) return null;
const metadata = attribute_lookup[fix_attribute_casing(this.node.name)];
if (metadata && metadata.applies_to && !metadata.applies_to.includes(this.parent.node.name)) return null;
return metadata;
}
get_class_name_text() {
const scoped_css = this.node.chunks.some((chunk: Text) => chunk.synthetic);
const rendered = this.render_chunks();

@ -354,7 +354,7 @@ export default class ElementWrapper extends Wrapper {
if (renderer.options.dev) {
const loc = renderer.locate(this.node.start);
block.chunks.hydrate.push(
b`@add_location(${this.var}, ${renderer.file_var}, ${loc.line}, ${loc.column}, ${this.node.start});`
b`@add_location(${this.var}, ${renderer.file_var}, ${loc.line - 1}, ${loc.column}, ${this.node.start});`
);
}
}
@ -573,8 +573,7 @@ export default class ElementWrapper extends Wrapper {
}
});
// @ts-ignore todo:
if (this.node.attributes.find(attr => attr.type === 'Spread')) {
if (this.node.attributes.some(attr => attr.is_spread)) {
this.add_spread_attributes(block);
return;
}
@ -591,21 +590,24 @@ export default class ElementWrapper extends Wrapper {
const initial_props = [];
const updates = [];
this.node.attributes
.filter(attr => attr.type === 'Attribute' || attr.type === 'Spread')
this.attributes
.forEach(attr => {
const condition = attr.dependencies.size > 0
? changed(Array.from(attr.dependencies))
const condition = attr.node.dependencies.size > 0
? changed(Array.from(attr.node.dependencies))
: null;
if (attr.is_spread) {
const snippet = attr.expression.manipulate(block);
if (attr.node.is_spread) {
const snippet = attr.node.expression.manipulate(block);
initial_props.push(snippet);
updates.push(condition ? x`${condition} && ${snippet}` : snippet);
} else {
const snippet = x`{ ${attr.name}: ${attr.get_value(block)} }`;
const metadata = attr.get_metadata();
const snippet = x`{ ${
(metadata && metadata.property_name) ||
fix_attribute_casing(attr.node.name)
}: ${attr.node.get_value(block)} }`;
initial_props.push(snippet);
updates.push(condition ? x`${condition} && ${snippet}` : snippet);
@ -807,6 +809,7 @@ export default class ElementWrapper extends Wrapper {
}
add_classes(block: Block) {
const has_spread = this.node.attributes.some(attr => attr.is_spread);
this.node.classes.forEach(class_directive => {
const { expression, name } = class_directive;
let snippet;
@ -822,7 +825,9 @@ export default class ElementWrapper extends Wrapper {
block.chunks.hydrate.push(updater);
if ((dependencies && dependencies.size > 0) || this.class_dependencies.length) {
if (has_spread) {
block.chunks.update.push(updater);
} else if ((dependencies && dependencies.size > 0) || this.class_dependencies.length) {
const all_dependencies = this.class_dependencies.concat(...dependencies);
const condition = changed(all_dependencies);

@ -271,8 +271,8 @@ export default class IfBlockWrapper extends Wrapper {
? b`
${snippet && (
dependencies.length > 0
? b`if ((${condition} == null) || ${changed(dependencies)}) ${condition} = !!(${snippet})`
: b`if (${condition} == null) ${condition} = !!(${snippet})`
? b`if (${condition} == null || ${changed(dependencies)}) ${condition} = !!${snippet}`
: b`if (${condition} == null) ${condition} = !!${snippet}`
)}
if (${condition}) return ${block.name};`
: b`return ${block.name};`)}
@ -388,7 +388,11 @@ export default class IfBlockWrapper extends Wrapper {
function ${select_block_type}(#changed, #ctx) {
${this.branches.map(({ dependencies, condition, snippet }, i) => condition
? b`
${snippet && b`if ((${condition} == null) || ${changed(dependencies)}) ${condition} = !!(${snippet})`}
${snippet && (
dependencies.length > 0
? b`if (${condition} == null || ${changed(dependencies)}) ${condition} = !!${snippet}`
: b`if (${condition} == null) ${condition} = !!${snippet}`
)}
if (${condition}) return ${i};`
: b`return ${i};`)}
${!has_else && b`return -1;`}

@ -137,12 +137,12 @@ export default class SlotWrapper extends Wrapper {
block.render_listeners(`_${slot.name}`);
block.event_listeners = listeners;
if (block.chunks.create) create.push(b`if (!${slot}) { ${block.chunks.create} }`);
if (block.chunks.claim) claim.push(b`if (!${slot}) { ${block.chunks.claim} }`);
if (block.chunks.hydrate) hydrate.push(b`if (!${slot}) { ${block.chunks.hydrate} }`);
if (block.chunks.mount) mount.push(b`if (!${slot}) { ${block.chunks.mount} }`);
if (block.chunks.update) update.push(b`if (!${slot}) { ${block.chunks.update} }`);
if (block.chunks.destroy) destroy.push(b`if (!${slot}) { ${block.chunks.destroy} }`);
if (block.chunks.create.length) create.push(b`if (!${slot}) { ${block.chunks.create} }`);
if (block.chunks.claim.length) claim.push(b`if (!${slot}) { ${block.chunks.claim} }`);
if (block.chunks.hydrate.length) hydrate.push(b`if (!${slot}) { ${block.chunks.hydrate} }`);
if (block.chunks.mount.length) mount.push(b`if (!${slot}) { ${block.chunks.mount} }`);
if (block.chunks.update.length) update.push(b`if (!${slot}) { ${block.chunks.update} }`);
if (block.chunks.destroy.length) destroy.push(b`if (!${slot}) { ${block.chunks.destroy} }`);
block.chunks.create = create;
block.chunks.claim = claim;
@ -155,9 +155,11 @@ export default class SlotWrapper extends Wrapper {
b`if (${slot}) ${slot}.c();`
);
block.chunks.claim.push(
b`if (${slot}) ${slot}.l(${parent_nodes});`
);
if (renderer.options.hydratable) {
block.chunks.claim.push(
b`if (${slot}) ${slot}.l(${parent_nodes});`
);
}
block.chunks.mount.push(b`
if (${slot}) {

@ -26,7 +26,7 @@ export default function create_debugging_comment(
}
const start = locate(c);
const loc = `(${start.line + 1}:${start.column})`;
const loc = `(${start.line}:${start.column})`;
return `${loc} ${source.slice(c, d)}`.replace(/\s/g, ' ');
}

@ -12,5 +12,5 @@ export default function(node: DebugTag, renderer: Renderer, options: RenderOptio
${node.expressions.map(e => p`${e.node.name}`)}
}`;
renderer.add_expression(x`@debug(${filename ? x`"${filename}"` : x`null`}, ${line}, ${column}, ${obj})`);
renderer.add_expression(x`@debug(${filename ? x`"${filename}"` : x`null`}, ${line - 1}, ${column}, ${obj})`);
}

@ -1,5 +1,4 @@
import { is_void } from '../../../utils/names';
import Attribute from '../../nodes/Attribute';
import Class from '../../nodes/Class';
import { get_attribute_value, get_class_attribute_value } from './shared/get_attribute_value';
import { get_slot_scope } from './shared/get_slot_scope';
@ -80,62 +79,61 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
let add_class_attribute = class_expression ? true : false;
if (node.attributes.find(attr => attr.is_spread)) {
if (node.attributes.some(attr => attr.is_spread)) {
// TODO dry this out
const args = [];
node.attributes.forEach(attribute => {
if (attribute.is_spread) {
args.push(attribute.expression.node);
} else {
if (attribute.name === 'value' && node.name === 'textarea') {
const name = attribute.name.toLowerCase();
if (name === 'value' && node.name.toLowerCase() === 'textarea') {
node_contents = get_attribute_value(attribute);
} else if (attribute.is_true) {
args.push(x`{ ${attribute.name}: true }`);
} else if (
boolean_attributes.has(attribute.name) &&
boolean_attributes.has(name) &&
attribute.chunks.length === 1 &&
attribute.chunks[0].type !== 'Text'
) {
// a boolean attribute with one non-Text chunk
args.push(x`{ ${attribute.name}: ${(attribute.chunks[0] as Expression).node} }`);
} else if (attribute.name === 'class' && class_expression) {
args.push(x`{ ${attribute.name}: ${(attribute.chunks[0] as Expression).node} || null }`);
} else if (name === 'class' && class_expression) {
// Add class expression
args.push(x`{ ${attribute.name}: [${get_class_attribute_value(attribute)}, ${class_expression}].join(' ').trim() }`);
} else {
args.push(x`{ ${attribute.name}: ${attribute.name === 'class' ? get_class_attribute_value(attribute) : get_attribute_value(attribute)} }`);
args.push(x`{ ${attribute.name}: ${(name === 'class' ? get_class_attribute_value : get_attribute_value)(attribute)} }`);
}
}
});
renderer.add_expression(x`@spread([${args}])`);
} else {
node.attributes.forEach((attribute: Attribute) => {
if (attribute.type !== 'Attribute') return;
if (attribute.name === 'value' && node.name === 'textarea') {
node.attributes.forEach(attribute => {
const name = attribute.name.toLowerCase();
if (name === 'value' && node.name.toLowerCase() === 'textarea') {
node_contents = get_attribute_value(attribute);
} else if (attribute.is_true) {
renderer.add_string(` ${attribute.name}`);
} else if (
boolean_attributes.has(attribute.name) &&
boolean_attributes.has(name) &&
attribute.chunks.length === 1 &&
attribute.chunks[0].type !== 'Text'
) {
// a boolean attribute with one non-Text chunk
renderer.add_string(` `);
renderer.add_expression(x`${(attribute.chunks[0] as Expression).node} ? "${attribute.name}" : ""`);
} else if (attribute.name === 'class' && class_expression) {
} else if (name === 'class' && class_expression) {
add_class_attribute = false;
renderer.add_string(` class="`);
renderer.add_string(` ${attribute.name}="`);
renderer.add_expression(x`[${get_class_attribute_value(attribute)}, ${class_expression}].join(' ').trim()`);
renderer.add_string(`"`);
} else if (attribute.chunks.length === 1 && attribute.chunks[0].type !== 'Text') {
const { name } = attribute;
const snippet = (attribute.chunks[0] as Expression).node;
renderer.add_expression(x`@add_attribute("${name}", ${snippet}, ${boolean_attributes.has(name) ? 1 : 0})`);
renderer.add_expression(x`@add_attribute("${attribute.name}", ${snippet}, ${boolean_attributes.has(name) ? 1 : 0})`);
} else {
renderer.add_string(` ${attribute.name}="`);
renderer.add_expression(attribute.name === 'class' ? get_class_attribute_value(attribute) : get_attribute_value(attribute));
renderer.add_expression((name === 'class' ? get_class_attribute_value : get_attribute_value)(attribute));
renderer.add_string(`"`);
}
});

@ -98,6 +98,8 @@ export default function ssr(
: b`
let ${left} = ${right}`;
}
} else { // TODO do not add label if it's not referenced
statement = b`$: { ${statement} }`;
}
return statement;

@ -4,13 +4,11 @@ const Parser = acorn.Parser;
export const parse = (source: string) => Parser.parse(source, {
sourceType: 'module',
// @ts-ignore TODO pending release of fixed types
ecmaVersion: 11,
locations: true
});
export const parse_expression_at = (source: string, index: number) => Parser.parseExpressionAt(source, index, {
// @ts-ignore TODO pending release of fixed types
ecmaVersion: 11,
locations: true
});

@ -45,7 +45,7 @@ export default function read_script(parser: Parser, start: number, attributes: N
let ast: Program;
try {
ast = acorn.parse(source);
ast = acorn.parse(source) as any as Program;
} catch (err) {
parser.acorn_error(err);
}

@ -160,57 +160,47 @@ export default function mustache(parser: Parser) {
parser.stack.push(block.else);
}
} else if (parser.eat(':then')) {
// TODO DRY out this and the next section
const pending_block = parser.current();
if (pending_block.type === 'PendingBlock') {
pending_block.end = start;
parser.stack.pop();
const await_block = parser.current();
} else if (parser.match(':then') || parser.match(':catch')) {
const block = parser.current();
const is_then = parser.eat(':then') || !parser.eat(':catch');
if (!parser.eat('}')) {
parser.require_whitespace();
await_block.value = parser.read_identifier();
parser.allow_whitespace();
parser.eat('}', true);
if (is_then) {
if (block.type !== 'PendingBlock') {
parser.error({
code: `invalid-then-placement`,
message: 'Cannot have an {:then} block outside an {#await ...} block'
});
}
const then_block: TemplateNode = {
start,
end: null,
type: 'ThenBlock',
children: [],
skip: false
};
await_block.then = then_block;
parser.stack.push(then_block);
}
} else if (parser.eat(':catch')) {
const then_block = parser.current();
if (then_block.type === 'ThenBlock') {
then_block.end = start;
parser.stack.pop();
const await_block = parser.current();
if (!parser.eat('}')) {
parser.require_whitespace();
await_block.error = parser.read_identifier();
parser.allow_whitespace();
parser.eat('}', true);
} else {
if (block.type !== 'ThenBlock' && block.type !== 'PendingBlock') {
parser.error({
code: `invalid-catch-placement`,
message: 'Cannot have an {:catch} block outside an {#await ...} block'
});
}
}
const catch_block: TemplateNode = {
start,
end: null,
type: 'CatchBlock',
children: [],
skip: false
};
block.end = start;
parser.stack.pop();
const await_block = parser.current();
await_block.catch = catch_block;
parser.stack.push(catch_block);
if (!parser.eat('}')) {
parser.require_whitespace();
await_block[is_then ? 'value': 'error'] = parser.read_identifier();
parser.allow_whitespace();
parser.eat('}', true);
}
const new_block: TemplateNode = {
start,
end: null,
type: is_then ? 'ThenBlock': 'CatchBlock',
children: [],
skip: false
};
await_block[is_then ? 'then' : 'catch'] = new_block;
parser.stack.push(new_block);
} else if (parser.eat('#')) {
// {#if foo}, {#each foo} or {#await foo}
let type;

@ -81,7 +81,7 @@ export default function tag(parser: Parser) {
parser.current().children.length
) {
parser.error({
code: `invalid-${name.slice(7)}-content`,
code: `invalid-${slug}-content`,
message: `<${name}> cannot have children`
}, parser.current().children[0].start);
}

@ -86,14 +86,16 @@ export function self(fn) {
export function attr(node: Element, attribute: string, value?: string) {
if (value == null) node.removeAttribute(attribute);
else node.setAttribute(attribute, value);
else if (node.getAttribute(attribute) !== value) node.setAttribute(attribute, value);
}
export function set_attributes(node: Element & ElementCSSInlineStyle, attributes: { [x: string]: string }) {
// @ts-ignore
const descriptors = Object.getOwnPropertyDescriptors(node.__proto__);
for (const key in attributes) {
if (key === 'style') {
if (attributes[key] == null) {
node.removeAttribute(key);
} else if (key === 'style') {
node.style.cssText = attributes[key];
} else if (descriptors[key] && descriptors[key].set) {
node[key] = attributes[key];

@ -13,7 +13,7 @@ export function spread(args) {
if (invalid_attribute_name_character.test(name)) return;
const value = attributes[name];
if (value === undefined) return;
if (value == null) return;
if (value === true) str += " " + name;
const escaped = String(value)

@ -122,16 +122,30 @@ type StoresValues<T> = T extends Readable<infer U> ? U :
/**
* Derived value store by synchronizing one or more readable stores and
* applying an aggregation function over its input values.
* @param {Stores} stores input stores
* @param {function(Stores=, function(*)=):*}fn function callback that aggregates the values
* @param {*=}initial_value when used asynchronously
*
* @param stores - input stores
* @param fn - function callback that aggregates the values
*/
export function derived<T, S extends Stores>(
export function derived<S extends Stores, T>(
stores: S,
fn: (values: StoresValues<S>, set: Subscriber<T>) => T | Unsubscriber | void,
initial_value?: T,
): Readable<T> {
fn: (values: StoresValues<S>) => T
): Readable<T>;
/**
* Derived value store by synchronizing one or more readable stores and
* applying an aggregation function over its input values.
*
* @param stores - input stores
* @param fn - function callback that aggregates the values
* @param initial_value - when used asynchronously
*/
export function derived<S extends Stores, T>(
stores: S,
fn: (values: StoresValues<S>, set: (value: T) => void) => Unsubscriber | void,
initial_value?: T
): Readable<T>;
export function derived<T>(stores: Stores, fn: Function, initial_value?: T): Readable<T> {
const single = !Array.isArray(stores);
const stores_array: Array<Readable<any>> = single
? [stores as Readable<any>]
@ -141,7 +155,7 @@ export function derived<T, S extends Stores>(
return readable(initial_value, (set) => {
let inited = false;
const values: StoresValues<S> = [] as StoresValues<S>;
const values = [];
let pending = 0;
let cleanup = noop;

@ -37,7 +37,7 @@ function create(code) {
}
describe('css', () => {
fs.readdirSync('test/css/samples').forEach(dir => {
fs.readdirSync(`${__dirname}/samples`).forEach(dir => {
if (dir[0] === '.') return;
// add .solo to a sample directory name to only run that test
@ -51,7 +51,7 @@ describe('css', () => {
(solo ? it.only : skip ? it.skip : it)(dir, () => {
const config = try_require(`./samples/${dir}/_config.js`) || {};
const input = fs
.readFileSync(`test/css/samples/${dir}/input.svelte`, 'utf-8')
.readFileSync(`${__dirname}/samples/${dir}/input.svelte`, 'utf-8')
.replace(/\s+$/, '');
const expected_warnings = (config.warnings || []).map(normalize_warning);
@ -74,10 +74,10 @@ describe('css', () => {
assert.deepEqual(dom_warnings, ssr_warnings);
assert.deepEqual(dom_warnings.map(normalize_warning), expected_warnings);
fs.writeFileSync(`test/css/samples/${dir}/_actual.css`, dom.css.code);
fs.writeFileSync(`${__dirname}/samples/${dir}/_actual.css`, dom.css.code);
const expected = {
html: read(`test/css/samples/${dir}/expected.html`),
css: read(`test/css/samples/${dir}/expected.css`)
html: read(`${__dirname}/samples/${dir}/expected.html`),
css: read(`${__dirname}/samples/${dir}/expected.css`)
};
assert.equal(dom.css.code.replace(/svelte(-ref)?-[a-z0-9]+/g, (m, $1) => $1 ? m : 'svelte-xyz'), expected.css);
@ -112,7 +112,7 @@ describe('css', () => {
new ClientComponent({ target, props: config.props });
const html = target.innerHTML;
fs.writeFileSync(`test/css/samples/${dir}/_actual.html`, html);
fs.writeFileSync(`${__dirname}/samples/${dir}/_actual.html`, html);
assert.equal(
normalizeHtml(window, html.replace(/svelte(-ref)?-[a-z0-9]+/g, (m, $1) => $1 ? m : 'svelte-xyz')),

@ -14,7 +14,7 @@ const page = `
</body>
`;
const assert = fs.readFileSync('test/custom-elements/assert.js', 'utf-8');
const assert = fs.readFileSync(`${__dirname}/assert.js`, 'utf-8');
describe('custom-elements', function() {
this.timeout(10000);
@ -53,7 +53,7 @@ describe('custom-elements', function() {
await browser.close();
});
fs.readdirSync('test/custom-elements/samples').forEach(dir => {
fs.readdirSync(`${__dirname}/samples`).forEach(dir => {
if (dir[0] === '.') return;
const solo = /\.solo$/.test(dir);
@ -67,7 +67,7 @@ describe('custom-elements', function() {
const expected_warnings = config.warnings || [];
const bundle = await rollup({
input: `test/custom-elements/samples/${dir}/test.js`,
input: `${__dirname}/samples/${dir}/test.js`,
plugins: [
{
resolveId(importee) {

@ -47,7 +47,7 @@ describe('hydration', () => {
}
(config.skip ? it.skip : config.solo ? it.only : it)(dir, () => {
const cwd = path.resolve(`test/hydration/samples/${dir}`);
const cwd = path.resolve(`${__dirname}/samples/${dir}`);
compileOptions = config.compileOptions || {};
@ -96,7 +96,7 @@ describe('hydration', () => {
});
}
fs.readdirSync('test/hydration/samples').forEach(dir => {
fs.readdirSync(`${__dirname}/samples`).forEach(dir => {
runTest(dir, null);
});
});

@ -4,7 +4,7 @@ import * as path from "path";
import { loadConfig, svelte } from "../helpers.js";
describe("js", () => {
fs.readdirSync("test/js/samples").forEach(dir => {
fs.readdirSync(`${__dirname}/samples`).forEach(dir => {
if (dir[0] === ".") return;
// add .solo to a sample directory name to only run that test
@ -15,7 +15,7 @@ describe("js", () => {
}
(solo ? it.only : it)(dir, () => {
dir = path.resolve("test/js/samples", dir);
dir = path.resolve(`${__dirname}/samples`, dir);
const config = loadConfig(`${dir}/_config.js`);
const input = fs.readFileSync(`${dir}/input.svelte`, "utf-8").replace(/\s+$/, "");

@ -41,7 +41,7 @@ function create_fragment(ctx) {
function instance($$self, $$props, $$invalidate) {
let $foo;
const foo = writable(0);
component_subscribe($$self, foo, $$value => $$invalidate("$foo", $foo = $$value));
component_subscribe($$self, foo, value => $$invalidate("$foo", $foo = value));
return { foo, $foo };
}

@ -60,7 +60,7 @@ function create_each_block(ctx) {
}
function create_fragment(ctx) {
let each_anchor;
let each_1_anchor;
let each_value = things;
let each_blocks = [];
@ -74,7 +74,7 @@ function create_fragment(ctx) {
each_blocks[i].c();
}
each_anchor = empty();
each_1_anchor = empty();
},
l: function claim(nodes) {
throw new Error("options.hydrate only works if the component was compiled with the `hydratable: true` option");
@ -84,7 +84,7 @@ function create_fragment(ctx) {
each_blocks[i].m(target, anchor);
}
insert_dev(target, each_anchor, anchor);
insert_dev(target, each_1_anchor, anchor);
},
p: function update(changed, ctx) {
if (changed.things) {
@ -99,7 +99,7 @@ function create_fragment(ctx) {
} else {
each_blocks[i] = create_each_block(child_ctx);
each_blocks[i].c();
each_blocks[i].m(each_anchor.parentNode, each_anchor);
each_blocks[i].m(each_1_anchor.parentNode, each_1_anchor);
}
}
@ -114,7 +114,7 @@ function create_fragment(ctx) {
o: noop,
d: function destroy(detaching) {
destroy_each(each_blocks, detaching);
if (detaching) detach_dev(each_anchor);
if (detaching) detach_dev(each_1_anchor);
}
};

@ -43,7 +43,7 @@ function create_each_block(ctx) {
}
function create_fragment(ctx) {
let each_anchor;
let each_1_anchor;
let each_value = ctx.createElement;
let each_blocks = [];
@ -57,14 +57,14 @@ function create_fragment(ctx) {
each_blocks[i].c();
}
each_anchor = empty();
each_1_anchor = empty();
},
m(target, anchor) {
for (let i = 0; i < each_blocks.length; i += 1) {
each_blocks[i].m(target, anchor);
}
insert(target, each_anchor, anchor);
insert(target, each_1_anchor, anchor);
},
p(changed, ctx) {
if (changed.createElement) {
@ -79,7 +79,7 @@ function create_fragment(ctx) {
} else {
each_blocks[i] = create_each_block(child_ctx);
each_blocks[i].c();
each_blocks[i].m(each_anchor.parentNode, each_anchor);
each_blocks[i].m(each_1_anchor.parentNode, each_1_anchor);
}
}
@ -94,7 +94,7 @@ function create_fragment(ctx) {
o: noop,
d(detaching) {
destroy_each(each_blocks, detaching);
if (detaching) detach(each_anchor);
if (detaching) detach(each_1_anchor);
}
};
}

@ -43,7 +43,7 @@ function create_each_block(ctx) {
}
function create_fragment(ctx) {
let each_anchor;
let each_1_anchor;
let each_value = [ctx.a, ctx.b, ctx.c, ctx.d, ctx.e];
let each_blocks = [];
@ -57,14 +57,14 @@ function create_fragment(ctx) {
each_blocks[i].c();
}
each_anchor = empty();
each_1_anchor = empty();
},
m(target, anchor) {
for (let i = 0; i < 5; i += 1) {
each_blocks[i].m(target, anchor);
}
insert(target, each_anchor, anchor);
insert(target, each_1_anchor, anchor);
},
p(changed, ctx) {
if (changed.a || changed.b || changed.c || changed.d || changed.e) {
@ -79,7 +79,7 @@ function create_fragment(ctx) {
} else {
each_blocks[i] = create_each_block(child_ctx);
each_blocks[i].c();
each_blocks[i].m(each_anchor.parentNode, each_anchor);
each_blocks[i].m(each_1_anchor.parentNode, each_1_anchor);
}
}
@ -92,7 +92,7 @@ function create_fragment(ctx) {
o: noop,
d(detaching) {
destroy_each(each_blocks, detaching);
if (detaching) detach(each_anchor);
if (detaching) detach(each_1_anchor);
}
};
}

@ -63,15 +63,15 @@ function create_each_block(key_1, ctx) {
function create_fragment(ctx) {
let each_blocks = [];
let each_lookup = new Map();
let each_anchor;
let each_1_lookup = new Map();
let each_1_anchor;
let each_value = ctx.things;
const get_key = ctx => ctx.thing.id;
for (let i = 0; i < each_value.length; i += 1) {
let child_ctx = get_each_context(ctx, each_value, i);
let key = get_key(child_ctx);
each_lookup.set(key, each_blocks[i] = create_each_block(key, child_ctx));
each_1_lookup.set(key, each_blocks[i] = create_each_block(key, child_ctx));
}
return {
@ -80,19 +80,19 @@ function create_fragment(ctx) {
each_blocks[i].c();
}
each_anchor = empty();
each_1_anchor = empty();
},
m(target, anchor) {
for (let i = 0; i < each_blocks.length; i += 1) {
each_blocks[i].m(target, anchor);
}
insert(target, each_anchor, anchor);
insert(target, each_1_anchor, anchor);
},
p(changed, ctx) {
const each_value = ctx.things;
for (let i = 0; i < each_blocks.length; i += 1) each_blocks[i].r();
each_blocks = update_keyed_each(each_blocks, changed, get_key, 1, ctx, each_value, each_lookup, each_anchor.parentNode, fix_and_destroy_block, create_each_block, each_anchor, get_each_context);
each_blocks = update_keyed_each(each_blocks, changed, 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,
@ -102,7 +102,7 @@ function create_fragment(ctx) {
each_blocks[i].d(detaching);
}
if (detaching) detach(each_anchor);
if (detaching) detach(each_1_anchor);
}
};
}

@ -48,15 +48,15 @@ function create_each_block(key_1, ctx) {
function create_fragment(ctx) {
let each_blocks = [];
let each_lookup = new Map();
let each_anchor;
let each_1_lookup = new Map();
let each_1_anchor;
let each_value = ctx.things;
const get_key = ctx => ctx.thing.id;
for (let i = 0; i < each_value.length; i += 1) {
let child_ctx = get_each_context(ctx, each_value, i);
let key = get_key(child_ctx);
each_lookup.set(key, each_blocks[i] = create_each_block(key, child_ctx));
each_1_lookup.set(key, each_blocks[i] = create_each_block(key, child_ctx));
}
return {
@ -65,18 +65,18 @@ function create_fragment(ctx) {
each_blocks[i].c();
}
each_anchor = empty();
each_1_anchor = empty();
},
m(target, anchor) {
for (let i = 0; i < each_blocks.length; i += 1) {
each_blocks[i].m(target, anchor);
}
insert(target, each_anchor, anchor);
insert(target, each_1_anchor, anchor);
},
p(changed, ctx) {
const each_value = ctx.things;
each_blocks = update_keyed_each(each_blocks, changed, get_key, 1, ctx, each_value, each_lookup, each_anchor.parentNode, destroy_block, create_each_block, each_anchor, get_each_context);
each_blocks = update_keyed_each(each_blocks, changed, 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,
@ -85,7 +85,7 @@ function create_fragment(ctx) {
each_blocks[i].d(detaching);
}
if (detaching) detach(each_anchor);
if (detaching) detach(each_1_anchor);
}
};
}

@ -14,7 +14,6 @@ function create_fragment(ctx) {
let div0;
let t;
let div1;
let div1_style_value;
return {
c() {
@ -22,7 +21,7 @@ function create_fragment(ctx) {
t = space();
div1 = element("div");
attr(div0, "style", ctx.style);
attr(div1, "style", div1_style_value = "" + (ctx.key + ": " + ctx.value));
attr(div1, "style", "" + (ctx.key + ": " + ctx.value));
},
m(target, anchor) {
insert(target, div0, anchor);
@ -34,8 +33,8 @@ function create_fragment(ctx) {
attr(div0, "style", ctx.style);
}
if ((changed.key || changed.value) && div1_style_value !== (div1_style_value = "" + (ctx.key + ": " + ctx.value))) {
attr(div1, "style", div1_style_value);
if (changed.key || changed.value) {
attr(div1, "style", "" + (ctx.key + ": " + ctx.value));
}
},
i: noop,

@ -3,7 +3,7 @@ import * as fs from 'fs';
import { svelte, tryToLoadJson } from '../helpers.js';
describe('parse', () => {
fs.readdirSync('test/parser/samples').forEach(dir => {
fs.readdirSync(`${__dirname}/samples`).forEach(dir => {
if (dir[0] === '.') return;
// add .solo to a sample directory name to only run that test
@ -16,18 +16,18 @@ describe('parse', () => {
}
(solo ? it.only : it)(dir, () => {
const options = tryToLoadJson(`test/parser/samples/${dir}/options.json`) || {};
const options = tryToLoadJson(`${__dirname}/samples/${dir}/options.json`) || {};
const input = fs.readFileSync(`test/parser/samples/${dir}/input.svelte`, 'utf-8').replace(/\s+$/, '');
const expectedOutput = tryToLoadJson(`test/parser/samples/${dir}/output.json`);
const expectedError = tryToLoadJson(`test/parser/samples/${dir}/error.json`);
const input = fs.readFileSync(`${__dirname}/samples/${dir}/input.svelte`, 'utf-8').replace(/\s+$/, '');
const expectedOutput = tryToLoadJson(`${__dirname}/samples/${dir}/output.json`);
const expectedError = tryToLoadJson(`${__dirname}/samples/${dir}/error.json`);
try {
const { ast } = svelte.compile(input, Object.assign(options, {
generate: false
}));
fs.writeFileSync(`test/parser/samples/${dir}/_actual.json`, JSON.stringify(ast, null, '\t'));
fs.writeFileSync(`${__dirname}/samples/${dir}/_actual.json`, JSON.stringify(ast, null, '\t'));
assert.deepEqual(ast.html, expectedOutput.html);
assert.deepEqual(ast.css, expectedOutput.css);

@ -0,0 +1,5 @@
{#await thePromise}
<p>loading...</p>
{:catch theError}
<p>oh no! {theError.message}</p>
{/await}

@ -0,0 +1,168 @@
{
"html": {
"start": 0,
"end": 99,
"type": "Fragment",
"children": [
{
"start": 0,
"end": 99,
"type": "AwaitBlock",
"expression": {
"type": "Identifier",
"start": 8,
"end": 18,
"loc": {
"start": {
"line": 1,
"column": 8
},
"end": {
"line": 1,
"column": 18
}
},
"name": "thePromise"
},
"value": null,
"error": "theError",
"pending": {
"start": 19,
"end": 39,
"type": "PendingBlock",
"children": [
{
"start": 19,
"end": 21,
"type": "Text",
"raw": "\n\t",
"data": "\n\t"
},
{
"start": 21,
"end": 38,
"type": "Element",
"name": "p",
"attributes": [],
"children": [
{
"start": 24,
"end": 34,
"type": "Text",
"raw": "loading...",
"data": "loading..."
}
]
},
{
"start": 38,
"end": 39,
"type": "Text",
"raw": "\n",
"data": "\n"
}
],
"skip": false
},
"then": {
"start": null,
"end": null,
"type": "ThenBlock",
"children": [],
"skip": true
},
"catch": {
"start": 39,
"end": 91,
"type": "CatchBlock",
"children": [
{
"start": 56,
"end": 58,
"type": "Text",
"raw": "\n\t",
"data": "\n\t"
},
{
"start": 58,
"end": 90,
"type": "Element",
"name": "p",
"attributes": [],
"children": [
{
"start": 61,
"end": 68,
"type": "Text",
"raw": "oh no! ",
"data": "oh no! "
},
{
"start": 68,
"end": 86,
"type": "MustacheTag",
"expression": {
"type": "MemberExpression",
"start": 69,
"end": 85,
"loc": {
"start": {
"line": 4,
"column": 12
},
"end": {
"line": 4,
"column": 28
}
},
"object": {
"type": "Identifier",
"start": 69,
"end": 77,
"loc": {
"start": {
"line": 4,
"column": 12
},
"end": {
"line": 4,
"column": 20
}
},
"name": "theError"
},
"property": {
"type": "Identifier",
"start": 78,
"end": 85,
"loc": {
"start": {
"line": 4,
"column": 21
},
"end": {
"line": 4,
"column": 28
}
},
"name": "message"
},
"computed": false
}
}
]
},
{
"start": 90,
"end": 91,
"type": "Text",
"raw": "\n",
"data": "\n"
}
],
"skip": false
}
}
]
}
}

@ -0,0 +1,10 @@
{
"code": "invalid-catch-placement",
"message": "Cannot have an {:catch} block outside an {#await ...} block",
"start": {
"line": 1,
"column": 7,
"character": 7
},
"pos": 7
}

@ -0,0 +1,10 @@
{
"code": "invalid-then-placement",
"message": "Cannot have an {:then} block outside an {#await ...} block",
"start": {
"line": 1,
"column": 6,
"character": 6
},
"pos": 6
}

@ -3,21 +3,21 @@ import * as assert from 'assert';
import { loadConfig, svelte } from '../helpers.js';
describe('preprocess', () => {
fs.readdirSync('test/preprocess/samples').forEach(dir => {
fs.readdirSync(`${__dirname}/samples`).forEach(dir => {
if (dir[0] === '.') return;
const config = loadConfig(`./preprocess/samples/${dir}/_config.js`);
const config = loadConfig(`${__dirname}/samples/${dir}/_config.js`);
if (config.solo && process.env.CI) {
throw new Error('Forgot to remove `solo: true` from test');
}
(config.skip ? it.skip : config.solo ? it.only : it)(dir, async () => {
const input = fs.readFileSync(`test/preprocess/samples/${dir}/input.svelte`, 'utf-8');
const expected = fs.readFileSync(`test/preprocess/samples/${dir}/output.svelte`, 'utf-8');
const input = fs.readFileSync(`${__dirname}/samples/${dir}/input.svelte`, 'utf-8');
const expected = fs.readFileSync(`${__dirname}/samples/${dir}/output.svelte`, 'utf-8');
const result = await svelte.preprocess(input, config.preprocess);
fs.writeFileSync(`test/preprocess/samples/${dir}/_actual.html`, result.code);
fs.writeFileSync(`${__dirname}/samples/${dir}/_actual.html`, result.code);
assert.equal(result.code, expected);

@ -49,7 +49,7 @@ describe("runtime", () => {
function runTest(dir, hydrate) {
if (dir[0] === ".") return;
const config = loadConfig(`./runtime/samples/${dir}/_config.js`);
const config = loadConfig(`${__dirname}/samples/${dir}/_config.js`);
if (hydrate && config.skip_if_hydrate) return;
@ -67,7 +67,7 @@ describe("runtime", () => {
compile = (config.preserveIdentifiers ? svelte : svelte$).compile;
const cwd = path.resolve(`test/runtime/samples/${dir}`);
const cwd = path.resolve(`${__dirname}/samples/${dir}`);
compileOptions = config.compileOptions || {};
compileOptions.format = 'cjs';
@ -191,10 +191,12 @@ describe("runtime", () => {
assert.equal(config.error, err.message);
}
} else {
failed.add(dir);
showOutput(cwd, compileOptions, compile); // eslint-disable-line no-console
throw err;
}
}).catch(err => {
failed.add(dir);
showOutput(cwd, compileOptions, compile); // eslint-disable-line no-console
throw err;
})
.catch(err => {
// print a clickable link to open the directory
@ -213,7 +215,7 @@ describe("runtime", () => {
});
}
fs.readdirSync("test/runtime/samples").forEach(dir => {
fs.readdirSync(`${__dirname}/samples`).forEach(dir => {
runTest(dir, false);
runTest(dir, true);
});

@ -0,0 +1,3 @@
export default {
html: `<input readonly>`
};

@ -0,0 +1,3 @@
export default {
html: `<input>`
};

@ -0,0 +1 @@
<input {...{ foo: null }} readonly={false} required={false} disabled={null}>

@ -0,0 +1,4 @@
export default {
skip_if_ssr: true,
html: `<div>object</div>`
};

@ -0,0 +1,6 @@
<script>
import { writable } from 'svelte/store';
const foo = writable();
</script>
<div bind:this={$foo}>{typeof $foo}</div>

@ -0,0 +1,18 @@
export default {
props: {
primary: true,
},
html: `<div class="test-class primary" role="button"></div>`,
test({ assert, component, target, window }) {
component.primary = true;
assert.htmlEqual(
target.innerHTML,
`
<div class="test-class primary" role="button"></div>
`
);
},
};

@ -0,0 +1,11 @@
<script>
let primary = true;
let elem;
</script>
<div
bind:this={elem}
class="test-class"
class:primary
{...{ role: 'button' }}
></div>

@ -0,0 +1,4 @@
export default {
html: `<text>hello world</text>`,
preserveIdentifiers: true,
};

@ -0,0 +1,5 @@
<script>
let foo = 'hello world'
</script>
<text>{foo}</text>

@ -0,0 +1,7 @@
export default {
html: `
<div>1</div>
<div>2</div>
<div>3</div>
`
};

@ -0,0 +1,9 @@
<script>
const arr = [1, 2, 3];
</script>
{#each arr as item ((() => item)())}
<div>
{item}
</div>
{/each}

@ -0,0 +1,3 @@
export default {
html: `blah blah blah blah`
};

@ -0,0 +1,32 @@
<script>
import { writable } from 'svelte/store';
const foo = writable(true);
</script>
{#if $foo}
blah
{:else}
{#if bar()}
<Bar/>
{/if}
{/if}
{#if $foo}
blah
{:else}
{#if bar}
<Baz/>
{/if}
{/if}
{#if $foo}
blah
{:else if bar()}
<Bar/>
{/if}
{#if $foo}
blah
{:else if bar}
<Bar/>
{/if}

@ -0,0 +1,3 @@
export default {
html: `<h1>1 2</h1>`
};

@ -0,0 +1,14 @@
<script>
let foo = 0;
let bar;
$: {
bar = foo + 1;
if (foo) {
break $;
}
bar = foo + 2;
}
foo = 1;
</script>
<h1>{foo} {bar}</h1>

@ -0,0 +1,7 @@
export default {
html: `<div class='foo bar'>hello</div>`,
test({ assert, component, target }) {
component.blah = 'goodbye';
assert.htmlEqual(target.innerHTML, `<div class='foo bar'>goodbye</div>`);
}
};

@ -0,0 +1,5 @@
<script>
export let blah = 'hello';
</script>
<div class='foo' class:bar={true} {...{}}>{blah}</div>

@ -0,0 +1,3 @@
export default {
html: `<input>`
};

@ -0,0 +1 @@
<input placeholder='foo' {...{ placeholder: null }}>

@ -1,4 +1,6 @@
export default {
compileOptions: { dev: true }, // tests `@validate_store` code generation
html: `
<p>42</p>
`

@ -0,0 +1,27 @@
let subscribeCalled = false;
const fakeStore = val => ({
subscribe: cb => {
cb(val);
return {
unsubscribe: () => {
subscribeCalled = true;
},
};
},
});
export default {
props: {
foo: fakeStore(1),
},
html: `
<h1>1</h1>
`,
async test({ assert, component, target }) {
component.foo = fakeStore(5);
return assert.htmlEqual(target.innerHTML, `<h1>5</h1>`);
},
};

@ -0,0 +1,6 @@
<script>
import { writable } from 'svelte/store';
export let foo = writable(0);
</script>
<h1>{$foo}</h1>

@ -30,7 +30,7 @@ describe("ssr", () => {
return setupHtmlEqual();
});
fs.readdirSync("test/server-side-rendering/samples").forEach(dir => {
fs.readdirSync(`${__dirname}/samples`).forEach(dir => {
if (dir[0] === ".") return;
// add .solo to a sample directory name to only run that test, or
@ -43,7 +43,7 @@ describe("ssr", () => {
}
(solo ? it.only : it)(dir, () => {
dir = path.resolve("test/server-side-rendering/samples", dir);
dir = path.resolve(`${__dirname}/samples`, dir);
try {
const Component = require(`${dir}/main.svelte`).default;

@ -6,7 +6,7 @@ import { SourceMapConsumer } from "source-map";
import { getLocator } from "locate-character";
describe("sourcemaps", () => {
fs.readdirSync("test/sourcemaps/samples").forEach(dir => {
fs.readdirSync(`${__dirname}/samples`).forEach(dir => {
if (dir[0] === ".") return;
// add .solo to a sample directory name to only run that test
@ -19,10 +19,10 @@ describe("sourcemaps", () => {
(solo ? it.only : skip ? it.skip : it)(dir, async () => {
const filename = path.resolve(
`test/sourcemaps/samples/${dir}/input.svelte`
`${__dirname}/samples/${dir}/input.svelte`
);
const outputFilename = path.resolve(
`test/sourcemaps/samples/${dir}/output`
`${__dirname}/samples/${dir}/output`
);
const input = fs.readFileSync(filename, "utf-8").replace(/\s+$/, "");

@ -3,7 +3,7 @@ import * as assert from 'assert';
import { svelte, loadConfig, tryToLoadJson } from '../helpers.js';
describe('stats', () => {
fs.readdirSync('test/stats/samples').forEach(dir => {
fs.readdirSync(`${__dirname}/samples`).forEach(dir => {
if (dir[0] === '.') return;
// add .solo to a sample directory name to only run that test
@ -15,12 +15,12 @@ describe('stats', () => {
}
(solo ? it.only : skip ? it.skip : it)(dir, () => {
const config = loadConfig(`./stats/samples/${dir}/_config.js`);
const filename = `test/stats/samples/${dir}/input.svelte`;
const config = loadConfig(`${__dirname}/samples/${dir}/_config.js`);
const filename = `${__dirname}/samples/${dir}/input.svelte`;
const input = fs.readFileSync(filename, 'utf-8').replace(/\s+$/, '');
const expectedError = tryToLoadJson(
`test/stats/samples/${dir}/error.json`
`${__dirname}/samples/${dir}/error.json`
);
let result;

@ -8,6 +8,14 @@ require('../internal');
console.clear();
glob('*/index.js', { cwd: 'test' }).forEach((file) => {
require('./' + file);
});
const test_folders = glob('*/index.js', { cwd: 'test' });
const solo_folders = test_folders.filter(folder => /\.solo/.test(folder));
if (solo_folders.length) {
if (process.env.CI) {
throw new Error('Forgot to remove `.solo` from test');
}
solo_folders.forEach(name => require('./' + name));
} else {
test_folders.forEach(name => require('./' + name));
}

@ -3,7 +3,7 @@ import * as assert from "assert";
import { svelte, loadConfig, tryToLoadJson } from "../helpers.js";
describe("validate", () => {
fs.readdirSync("test/validator/samples").forEach(dir => {
fs.readdirSync(`${__dirname}/samples`).forEach(dir => {
if (dir[0] === ".") return;
// add .solo to a sample directory name to only run that test
@ -15,11 +15,11 @@ describe("validate", () => {
}
(solo ? it.only : skip ? it.skip : it)(dir, () => {
const config = loadConfig(`./validator/samples/${dir}/_config.js`);
const config = loadConfig(`${__dirname}/samples/${dir}/_config.js`);
const input = fs.readFileSync(`test/validator/samples/${dir}/input.svelte`, "utf-8").replace(/\s+$/, "");
const expected_warnings = tryToLoadJson(`test/validator/samples/${dir}/warnings.json`) || [];
const expected_errors = tryToLoadJson(`test/validator/samples/${dir}/errors.json`);
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`);
let error;

@ -3,7 +3,7 @@ import * as assert from 'assert';
import { svelte, loadConfig, tryToLoadJson } from '../helpers.js';
describe('vars', () => {
fs.readdirSync('test/vars/samples').forEach(dir => {
fs.readdirSync(`${__dirname}/samples`).forEach(dir => {
if (dir[0] === '.') return;
// add .solo to a sample directory name to only run that test
@ -16,12 +16,12 @@ describe('vars', () => {
for (const generate of ['dom', 'ssr', false]) {
(solo ? it.only : skip ? it.skip : it)(`${dir}, generate: ${generate}`, () => {
const config = loadConfig(`./vars/samples/${dir}/_config.js`);
const filename = `test/vars/samples/${dir}/input.svelte`;
const config = loadConfig(`${__dirname}/samples/${dir}/_config.js`);
const filename = `${__dirname}/samples/${dir}/input.svelte`;
const input = fs.readFileSync(filename, 'utf-8').replace(/\s+$/, '');
const expectedError = tryToLoadJson(
`test/vars/samples/${dir}/error.json`
`${__dirname}/samples/${dir}/error.json`
);
let result;

Loading…
Cancel
Save