chore: add prettier (#8543)

pull/8572/head
Yuichiro Yamashita 2 years ago committed by GitHub
parent 573acf9073
commit b6a400a549
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,6 +1,6 @@
module.exports = { module.exports = {
root: true, root: true,
extends: '@sveltejs', extends: ['@sveltejs', 'prettier'],
settings: { settings: {
'import/core-modules': [ 'import/core-modules': [
'svelte', 'svelte',

@ -44,7 +44,7 @@ jobs:
with: with:
node-version: 16 node-version: 16
cache: pnpm cache: pnpm
- run: 'pnpm i && pnpm lint' - run: 'pnpm i && pnpm format:check && pnpm lint'
Unit: Unit:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
timeout-minutes: 10 timeout-minutes: 10

@ -0,0 +1,13 @@
/*
!/elements
!/scripts
# TODO: after launch new site, format site dir.
/site
!/src
src/compiler/compile/internal_exports.ts
!/test
/test/**/*.svelte
/test/**/_expected*
/test/**/_actual*
/test/**/expected*
/types

@ -0,0 +1,23 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"overrides": [
{
"files": ["*.svelte"],
"options": {
"bracketSameLine": false
}
},
{
"files": ["README.md", "packages/*/README.md"],
"options": {
"useTabs": false,
"tabWidth": 2
}
}
],
"pluginSearchDirs": ["."]
}

106
elements/index.d.ts vendored

@ -39,8 +39,9 @@ type Booleanish = boolean | 'true' | 'false';
// Event Handler Types // Event Handler Types
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
type EventHandler<E extends Event = Event, T extends EventTarget = Element> = type EventHandler<E extends Event = Event, T extends EventTarget = Element> = (
(event: E & { currentTarget: EventTarget & T}) => any; event: E & { currentTarget: EventTarget & T }
) => any;
export type ClipboardEventHandler<T extends EventTarget> = EventHandler<ClipboardEvent, T>; export type ClipboardEventHandler<T extends EventTarget> = EventHandler<ClipboardEvent, T>;
export type CompositionEventHandler<T extends EventTarget> = EventHandler<CompositionEvent, T>; export type CompositionEventHandler<T extends EventTarget> = EventHandler<CompositionEvent, T>;
@ -350,7 +351,19 @@ export interface AriaAttributes {
* Indicates what notifications the user agent will trigger when the accessibility tree within a live region is modified. * Indicates what notifications the user agent will trigger when the accessibility tree within a live region is modified.
* @see aria-atomic. * @see aria-atomic.
*/ */
'aria-relevant'?: 'additions' | 'additions removals' | 'additions text' | 'all' | 'removals' | 'removals additions' | 'removals text' | 'text' | 'text additions' | 'text removals' | undefined | null; 'aria-relevant'?:
| 'additions'
| 'additions removals'
| 'additions text'
| 'all'
| 'removals'
| 'removals additions'
| 'removals text'
| 'text'
| 'text additions'
| 'text removals'
| undefined
| null;
/** Indicates that user input is required on the element before a form may be submitted. */ /** Indicates that user input is required on the element before a form may be submitted. */
'aria-required'?: Booleanish | undefined | null; 'aria-required'?: Booleanish | undefined | null;
/** Defines a human-readable, author-localized description for the role of an element. */ /** Defines a human-readable, author-localized description for the role of an element. */
@ -477,7 +490,16 @@ export interface HTMLAttributes<T extends EventTarget> extends AriaAttributes, D
contextmenu?: string | undefined | null; contextmenu?: string | undefined | null;
dir?: string | undefined | null; dir?: string | undefined | null;
draggable?: Booleanish | undefined | null; draggable?: Booleanish | undefined | null;
enterkeyhint?: 'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send' | undefined | null; enterkeyhint?:
| 'enter'
| 'done'
| 'go'
| 'next'
| 'previous'
| 'search'
| 'send'
| undefined
| null;
hidden?: boolean | undefined | null; hidden?: boolean | undefined | null;
id?: string | undefined | null; id?: string | undefined | null;
lang?: string | undefined | null; lang?: string | undefined | null;
@ -526,7 +548,17 @@ export interface HTMLAttributes<T extends EventTarget> extends AriaAttributes, D
* Hints at the type of data that might be entered by the user while editing the element or its contents * Hints at the type of data that might be entered by the user while editing the element or its contents
* @see https://html.spec.whatwg.org/multipage/interaction.html#input-modalities:-the-inputmode-attribute * @see https://html.spec.whatwg.org/multipage/interaction.html#input-modalities:-the-inputmode-attribute
*/ */
inputmode?: 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search' | undefined | null; inputmode?:
| 'none'
| 'text'
| 'tel'
| 'url'
| 'email'
| 'numeric'
| 'decimal'
| 'search'
| undefined
| null;
/** /**
* Specify that a standard HTML element should behave like a defined custom built-in element * Specify that a standard HTML element should behave like a defined custom built-in element
* @see https://html.spec.whatwg.org/multipage/custom-elements.html#attr-is * @see https://html.spec.whatwg.org/multipage/custom-elements.html#attr-is
@ -554,7 +586,16 @@ export interface HTMLAttributes<T extends EventTarget> extends AriaAttributes, D
// SvelteKit // SvelteKit
'data-sveltekit-keepfocus'?: true | '' | 'off' | undefined | null; 'data-sveltekit-keepfocus'?: true | '' | 'off' | undefined | null;
'data-sveltekit-noscroll'?: true | '' | 'off' | undefined | null; 'data-sveltekit-noscroll'?: true | '' | 'off' | undefined | null;
'data-sveltekit-preload-code'?: true | '' | 'eager' | 'viewport' | 'hover' | 'tap' | 'off' | undefined | null; 'data-sveltekit-preload-code'?:
| true
| ''
| 'eager'
| 'viewport'
| 'hover'
| 'tap'
| 'off'
| undefined
| null;
'data-sveltekit-preload-data'?: true | '' | 'hover' | 'tap' | 'off' | undefined | null; 'data-sveltekit-preload-data'?: true | '' | 'hover' | 'tap' | 'off' | undefined | null;
'data-sveltekit-reload'?: true | '' | 'off' | undefined | null; 'data-sveltekit-reload'?: true | '' | 'off' | undefined | null;
'data-sveltekit-replacestate'?: true | '' | 'off' | undefined | null; 'data-sveltekit-replacestate'?: true | '' | 'off' | undefined | null;
@ -563,12 +604,7 @@ export interface HTMLAttributes<T extends EventTarget> extends AriaAttributes, D
[key: `data-${string}`]: any; [key: `data-${string}`]: any;
} }
export type HTMLAttributeAnchorTarget = export type HTMLAttributeAnchorTarget = '_self' | '_blank' | '_parent' | '_top' | (string & {});
| '_self'
| '_blank'
| '_parent'
| '_top'
| (string & {});
export interface HTMLAnchorAttributes extends HTMLAttributes<HTMLAnchorElement> { export interface HTMLAnchorAttributes extends HTMLAttributes<HTMLAnchorElement> {
download?: any; download?: any;
@ -844,7 +880,14 @@ export interface HTMLMenuAttributes extends HTMLAttributes<HTMLMenuElement> {
export interface HTMLMediaAttributes<T extends HTMLMediaElement> extends HTMLAttributes<T> { export interface HTMLMediaAttributes<T extends HTMLMediaElement> extends HTMLAttributes<T> {
autoplay?: boolean | undefined | null; autoplay?: boolean | undefined | null;
controls?: boolean | undefined | null; controls?: boolean | undefined | null;
controlslist?: 'nodownload' | 'nofullscreen' | 'noplaybackrate' | 'noremoteplayback' | (string & {}) | undefined | null; controlslist?:
| 'nodownload'
| 'nofullscreen'
| 'noplaybackrate'
| 'noremoteplayback'
| (string & {})
| undefined
| null;
crossorigin?: string | undefined | null; crossorigin?: string | undefined | null;
currenttime?: number | undefined | null; currenttime?: number | undefined | null;
defaultmuted?: boolean | undefined | null; defaultmuted?: boolean | undefined | null;
@ -1155,9 +1198,22 @@ export interface SVGAttributes<T extends EventTarget> extends AriaAttributes, DO
'accent-height'?: number | string | undefined | null; 'accent-height'?: number | string | undefined | null;
accumulate?: 'none' | 'sum' | undefined | null; accumulate?: 'none' | 'sum' | undefined | null;
additive?: 'replace' | 'sum' | undefined | null; additive?: 'replace' | 'sum' | undefined | null;
'alignment-baseline'?: 'auto' | 'baseline' | 'before-edge' | 'text-before-edge' | 'middle' | 'alignment-baseline'?:
'central' | 'after-edge' | 'text-after-edge' | 'ideographic' | 'alphabetic' | 'hanging' | | 'auto'
'mathematical' | 'inherit' | undefined | null; | 'baseline'
| 'before-edge'
| 'text-before-edge'
| 'middle'
| 'central'
| 'after-edge'
| 'text-after-edge'
| 'ideographic'
| 'alphabetic'
| 'hanging'
| 'mathematical'
| 'inherit'
| undefined
| null;
allowReorder?: 'no' | 'yes' | undefined | null; allowReorder?: 'no' | 'yes' | undefined | null;
alphabetic?: number | string | undefined | null; alphabetic?: number | string | undefined | null;
amplitude?: number | string | undefined | null; amplitude?: number | string | undefined | null;
@ -1604,15 +1660,27 @@ export interface SvelteHTMLElements {
'svelte:body': HTMLAttributes<HTMLElement>; 'svelte:body': HTMLAttributes<HTMLElement>;
'svelte:fragment': { slot?: string }; 'svelte:fragment': { slot?: string };
'svelte:options': { 'svelte:options': {
customElement?: string | undefined | { customElement?:
| string
| undefined
| {
tag: string; tag: string;
shadow?: 'open' | 'none' | undefined; shadow?: 'open' | 'none' | undefined;
props?: Record<string, { attribute?: string; reflect?: boolean; type?: 'String' | 'Boolean' | 'Number' | 'Array' | 'Object' }> | undefined; props?:
| Record<
string,
{
attribute?: string;
reflect?: boolean;
type?: 'String' | 'Boolean' | 'Number' | 'Array' | 'Object';
}
>
| undefined;
}; };
immutable?: boolean | undefined; immutable?: boolean | undefined;
accessors?: boolean | undefined; accessors?: boolean | undefined;
namespace?: string | undefined; namespace?: string | undefined;
[name: string]: any [name: string]: any;
}; };
'svelte:head': { [name: string]: any }; 'svelte:head': { [name: string]: any };

@ -81,6 +81,8 @@
}, },
"types": "types/runtime/index.d.ts", "types": "types/runtime/index.d.ts",
"scripts": { "scripts": {
"format:fix": "prettier . --cache --plugin-search-dir=. --write",
"format:check": "prettier . --cache --plugin-search-dir=. --check",
"test": "npm run test:unit && npm run test:integration && echo \"manually check that there are no type errors in test/types by opening the files in there\"", "test": "npm run test:unit && npm run test:integration && echo \"manually check that there are no type errors in test/types by opening the files in there\"",
"test:integration": "mocha --exit", "test:integration": "mocha --exit",
"test:unit": "mocha --config .mocharc.unit.js --exit", "test:unit": "mocha --config .mocharc.unit.js --exit",
@ -133,6 +135,7 @@
"code-red": "^1.0.0", "code-red": "^1.0.0",
"css-tree": "^2.3.1", "css-tree": "^2.3.1",
"eslint": "^8.35.0", "eslint": "^8.35.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-import": "^2.27.5", "eslint-plugin-import": "^2.27.5",
"eslint-plugin-svelte3": "^4.0.0", "eslint-plugin-svelte3": "^4.0.0",
"estree-walker": "^3.0.3", "estree-walker": "^3.0.3",
@ -143,6 +146,8 @@
"magic-string": "^0.30.0", "magic-string": "^0.30.0",
"mocha": "^10.2.0", "mocha": "^10.2.0",
"periscopic": "^3.1.0", "periscopic": "^3.1.0",
"prettier": "^2.8.8",
"prettier-plugin-svelte": "^2.10.0",
"puppeteer": "^19.8.5", "puppeteer": "^19.8.5",
"rollup": "^3.20.2", "rollup": "^3.20.2",
"source-map": "^0.7.4", "source-map": "^0.7.4",

@ -70,6 +70,9 @@ devDependencies:
eslint: eslint:
specifier: ^8.35.0 specifier: ^8.35.0
version: 8.35.0 version: 8.35.0
eslint-config-prettier:
specifier: ^8.8.0
version: 8.8.0(eslint@8.35.0)
eslint-plugin-import: eslint-plugin-import:
specifier: ^2.27.5 specifier: ^2.27.5
version: 2.27.5(@typescript-eslint/parser@5.58.0)(eslint@8.35.0) version: 2.27.5(@typescript-eslint/parser@5.58.0)(eslint@8.35.0)
@ -100,6 +103,12 @@ devDependencies:
periscopic: periscopic:
specifier: ^3.1.0 specifier: ^3.1.0
version: 3.1.0 version: 3.1.0
prettier:
specifier: ^2.8.8
version: 2.8.8
prettier-plugin-svelte:
specifier: ^2.10.0
version: 2.10.0(prettier@2.8.8)(svelte@3.58.0)
puppeteer: puppeteer:
specifier: ^19.8.5 specifier: ^19.8.5
version: 19.8.5(typescript@5.0.4) version: 19.8.5(typescript@5.0.4)
@ -1238,6 +1247,15 @@ packages:
source-map: 0.6.1 source-map: 0.6.1
dev: true dev: true
/eslint-config-prettier@8.8.0(eslint@8.35.0):
resolution: {integrity: sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==}
hasBin: true
peerDependencies:
eslint: '>=7.0.0'
dependencies:
eslint: 8.35.0
dev: true
/eslint-import-resolver-node@0.3.7: /eslint-import-resolver-node@0.3.7:
resolution: {integrity: sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==} resolution: {integrity: sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==}
dependencies: dependencies:
@ -2552,6 +2570,22 @@ packages:
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
dev: true dev: true
/prettier-plugin-svelte@2.10.0(prettier@2.8.8)(svelte@3.58.0):
resolution: {integrity: sha512-GXMY6t86thctyCvQq+jqElO+MKdB09BkL3hexyGP3Oi8XLKRFaJP1ud/xlWCZ9ZIa2BxHka32zhHfcuU+XsRQg==}
peerDependencies:
prettier: ^1.16.4 || ^2.0.0
svelte: ^3.2.0
dependencies:
prettier: 2.8.8
svelte: 3.58.0
dev: true
/prettier@2.8.8:
resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==}
engines: {node: '>=10.13.0'}
hasBin: true
dev: true
/progress@2.0.3: /progress@2.0.3:
resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==}
engines: {node: '>=0.4.0'} engines: {node: '>=0.4.0'}

@ -15,13 +15,14 @@ const GLOBAL_TS_PATH = './src/compiler/utils/globals.ts';
// before this script was introduced but could not be retrieved by this process. // before this script was introduced but could not be retrieved by this process.
const SPECIALS = ['global', 'globalThis', 'InternalError', 'process', 'undefined']; const SPECIALS = ['global', 'globalThis', 'InternalError', 'process', 'undefined'];
const get_url = (name) => `https://raw.githubusercontent.com/microsoft/TypeScript/main/lib/lib.${name}.d.ts`; const get_url = (name) =>
`https://raw.githubusercontent.com/microsoft/TypeScript/main/lib/lib.${name}.d.ts`;
const extract_name = (split) => split.match(/^[a-zA-Z0-9_$]+/)[0]; const extract_name = (split) => split.match(/^[a-zA-Z0-9_$]+/)[0];
const extract_functions_and_references = (name, data) => { const extract_functions_and_references = (name, data) => {
const functions = []; const functions = [];
const references = []; const references = [];
data.split('\n').forEach(line => { data.split('\n').forEach((line) => {
const trimmed = line.trim(); const trimmed = line.trim();
const split = trimmed.replace(/[\s+]/, ' ').split(' '); const split = trimmed.replace(/[\s+]/, ' ').split(' ');
if (split[0] === 'declare' && split[1] !== 'type') { if (split[0] === 'declare' && split[1] !== 'type') {
@ -35,13 +36,16 @@ const extract_functions_and_references = (name, data) => {
return { functions, references }; return { functions, references };
}; };
const do_get = (url) => new Promise((resolve, reject) => { const do_get = (url) =>
http.get(url, (res) => { new Promise((resolve, reject) => {
http
.get(url, (res) => {
let body = ''; let body = '';
res.setEncoding('utf8'); res.setEncoding('utf8');
res.on('data', (chunk) => body += chunk); res.on('data', (chunk) => (body += chunk));
res.on('end', () => resolve(body)); res.on('end', () => resolve(body));
}).on('error', (e) => { })
.on('error', (e) => {
console.error(e.message); console.error(e.message);
reject(e); reject(e);
}); });
@ -56,7 +60,7 @@ const get_functions = async (name) => {
const { functions, references } = extract_functions_and_references(name, body); const { functions, references } = extract_functions_and_references(name, body);
res.push(...functions); res.push(...functions);
const chile_functions = await Promise.all(references.map(get_functions)); const chile_functions = await Promise.all(references.map(get_functions));
chile_functions.forEach(i => res.push(...i)); chile_functions.forEach((i) => res.push(...i));
return res; return res;
}; };
@ -76,10 +80,11 @@ ${sorted.map((i) => `\t'${i}'`).join(',\n')}
const get_exists_globals = () => { const get_exists_globals = () => {
const regexp = /^\s*["'](.+)["'],?\s*$/; const regexp = /^\s*["'](.+)["'],?\s*$/;
return fs.readFileSync(GLOBAL_TS_PATH, 'utf8') return fs
.readFileSync(GLOBAL_TS_PATH, 'utf8')
.split('\n') .split('\n')
.filter(line => line.match(regexp)) .filter((line) => line.match(regexp))
.map(line => line.match(regexp)[1]); .map((line) => line.match(regexp)[1]);
}; };
(async () => { (async () => {

@ -1,4 +1,5 @@
const now = (typeof process !== 'undefined' && process.hrtime) const now =
typeof process !== 'undefined' && process.hrtime
? () => { ? () => {
const t = process.hrtime(); const t = process.hrtime();
return t[0] * 1e3 + t[1] / 1e6; return t[0] * 1e3 + t[1] / 1e6;
@ -14,10 +15,13 @@ interface Timing {
function collapse_timings(timings) { function collapse_timings(timings) {
const result = {}; const result = {};
timings.forEach(timing => { timings.forEach((timing) => {
result[timing.label] = Object.assign({ result[timing.label] = Object.assign(
{
total: timing.end - timing.start total: timing.end - timing.start
}, timing.children && collapse_timings(timing.children)); },
timing.children && collapse_timings(timing.children)
);
}); });
return result; return result;
} }
@ -52,7 +56,9 @@ export default class Stats {
stop(label) { stop(label) {
if (label !== this.current_timing.label) { if (label !== this.current_timing.label) {
throw new Error(`Mismatched timing labels (expected ${this.current_timing.label}, got ${label})`); throw new Error(
`Mismatched timing labels (expected ${this.current_timing.label}, got ${label})`
);
} }
this.current_timing.end = now(); this.current_timing.end = now();
@ -62,9 +68,12 @@ export default class Stats {
} }
render() { render() {
const timings = Object.assign({ const timings = Object.assign(
{
total: now() - this.start_time total: now() - this.start_time
}, collapse_timings(this.timings)); },
collapse_timings(this.timings)
);
return { return {
timings timings

@ -6,12 +6,7 @@ import { reserved, is_valid } from '../utils/names';
import globals from '../utils/globals'; import globals from '../utils/globals';
import { namespaces, valid_namespaces } from '../utils/namespaces'; import { namespaces, valid_namespaces } from '../utils/namespaces';
import create_module from './create_module'; import create_module from './create_module';
import { import { create_scopes, extract_names, Scope, extract_identifiers } from './utils/scope';
create_scopes,
extract_names,
Scope,
extract_identifiers
} from './utils/scope';
import Stylesheet from './css/Stylesheet'; import Stylesheet from './css/Stylesheet';
import { test } from '../config'; import { test } from '../config';
import Fragment from './nodes/Fragment'; import Fragment from './nodes/Fragment';
@ -26,7 +21,25 @@ import TemplateScope from './nodes/shared/TemplateScope';
import fuzzymatch from '../utils/fuzzymatch'; import fuzzymatch from '../utils/fuzzymatch';
import get_object from './utils/get_object'; import get_object from './utils/get_object';
import Slot from './nodes/Slot'; import Slot from './nodes/Slot';
import { Node, ImportDeclaration, ExportNamedDeclaration, Identifier, ExpressionStatement, AssignmentExpression, Literal, Property, RestElement, ExportDefaultDeclaration, ExportAllDeclaration, FunctionDeclaration, FunctionExpression, VariableDeclarator, ObjectExpression, Pattern, Expression } from 'estree'; import {
Node,
ImportDeclaration,
ExportNamedDeclaration,
Identifier,
ExpressionStatement,
AssignmentExpression,
Literal,
Property,
RestElement,
ExportDefaultDeclaration,
ExportAllDeclaration,
FunctionDeclaration,
FunctionExpression,
VariableDeclarator,
ObjectExpression,
Pattern,
Expression
} from 'estree';
import add_to_set from './utils/add_to_set'; import add_to_set from './utils/add_to_set';
import check_graph_for_cycles from './utils/check_graph_for_cycles'; import check_graph_for_cycles from './utils/check_graph_for_cycles';
import { print, b } from 'code-red'; import { print, b } from 'code-red';
@ -36,7 +49,10 @@ import Element from './nodes/Element';
import { clone } from '../utils/clone'; import { clone } from '../utils/clone';
import compiler_warnings from './compiler_warnings'; import compiler_warnings from './compiler_warnings';
import compiler_errors from './compiler_errors'; import compiler_errors from './compiler_errors';
import { extract_ignores_above_position, extract_svelte_ignore_from_comments } from '../utils/extract_svelte_ignore'; import {
extract_ignores_above_position,
extract_svelte_ignore_from_comments
} from '../utils/extract_svelte_ignore';
import check_enable_sourcemap from './utils/check_enable_sourcemap'; import check_enable_sourcemap from './utils/check_enable_sourcemap';
import Tag from './nodes/shared/Tag'; import Tag from './nodes/shared/Tag';
@ -48,7 +64,14 @@ interface ComponentOptions {
customElement?: { customElement?: {
tag: string | null; tag: string | null;
shadow?: 'open' | 'none'; shadow?: 'open' | 'none';
props?: Record<string, { attribute?: string; reflect?: boolean; type?: 'String' | 'Boolean' | 'Number' | 'Array' | 'Object' }>; props?: Record<
string,
{
attribute?: string;
reflect?: boolean;
type?: 'String' | 'Boolean' | 'Number' | 'Array' | 'Object';
}
>;
}; };
} }
@ -86,8 +109,8 @@ export default class Component {
hoistable_nodes: Set<Node> = new Set(); hoistable_nodes: Set<Node> = new Set();
node_for_declaration: Map<string, Node> = new Map(); node_for_declaration: Map<string, Node> = new Map();
partly_hoisted: Array<(Node | Node[])> = []; partly_hoisted: Array<Node | Node[]> = [];
fully_hoisted: Array<(Node | Node[])> = []; fully_hoisted: Array<Node | Node[]> = [];
reactive_declarations: Array<{ reactive_declarations: Array<{
assignees: Set<string>; assignees: Set<string>;
dependencies: Set<string>; dependencies: Set<string>;
@ -162,13 +185,9 @@ export default class Component {
}); });
this.stylesheet.validate(this); this.stylesheet.validate(this);
this.component_options = process_component_options( this.component_options = process_component_options(this, this.ast.html.children);
this,
this.ast.html.children
);
this.namespace = this.namespace =
namespaces[this.component_options.namespace] || namespaces[this.component_options.namespace] || this.component_options.namespace;
this.component_options.namespace;
if (compile_options.customElement) { if (compile_options.customElement) {
this.tag = this.component_options.customElement?.tag || compile_options.tag || this.name.name; this.tag = this.component_options.customElement?.tag || compile_options.tag || this.name.name;
@ -178,18 +197,26 @@ export default class Component {
this.walk_module_js(); this.walk_module_js();
this.push_ignores(this.ast.instance ? extract_ignores_above_position(this.ast.instance.start, this.ast.html.children) : []); this.push_ignores(
this.ast.instance
? extract_ignores_above_position(this.ast.instance.start, this.ast.html.children)
: []
);
this.walk_instance_js_pre_template(); this.walk_instance_js_pre_template();
this.pop_ignores(); this.pop_ignores();
this.fragment = new Fragment(this, ast.html); this.fragment = new Fragment(this, ast.html);
this.name = this.get_unique_name(name); this.name = this.get_unique_name(name);
this.push_ignores(this.ast.instance ? extract_ignores_above_position(this.ast.instance.start, this.ast.html.children) : []); this.push_ignores(
this.ast.instance
? extract_ignores_above_position(this.ast.instance.start, this.ast.html.children)
: []
);
this.walk_instance_js_post_template(); this.walk_instance_js_post_template();
this.pop_ignores(); this.pop_ignores();
this.elements.forEach(element => this.stylesheet.apply(element)); this.elements.forEach((element) => this.stylesheet.apply(element));
this.stylesheet.reify(); this.stylesheet.reify();
this.stylesheet.warn_on_unused_selectors(this); this.stylesheet.warn_on_unused_selectors(this);
} }
@ -341,17 +368,15 @@ export default class Component {
referenced_globals, referenced_globals,
this.imports, this.imports,
this.vars this.vars
.filter(variable => variable.module && variable.export_name) .filter((variable) => variable.module && variable.export_name)
.map(variable => ({ .map((variable) => ({
name: variable.name, name: variable.name,
as: variable.export_name as: variable.export_name
})), })),
this.exports_from this.exports_from
); );
css = compile_options.customElement css = compile_options.customElement ? { code: null, map: null } : result.css;
? { code: null, map: null }
: result.css;
const js_sourcemap_enabled = check_enable_sourcemap(compile_options.enableSourcemap, 'js'); const js_sourcemap_enabled = check_enable_sourcemap(compile_options.enableSourcemap, 'js');
@ -365,15 +390,15 @@ export default class Component {
sourceMapSource: sourcemap_source_filename sourceMapSource: sourcemap_source_filename
}); });
js.map.sources = [ js.map.sources = [sourcemap_source_filename];
sourcemap_source_filename
];
js.map.sourcesContent = [ js.map.sourcesContent = [this.source];
this.source
];
js.map = apply_preprocessor_sourcemap(sourcemap_source_filename, js.map, compile_options.sourcemap as (string | RawSourceMap | DecodedSourceMap)); js.map = apply_preprocessor_sourcemap(
sourcemap_source_filename,
js.map,
compile_options.sourcemap as string | RawSourceMap | DecodedSourceMap
);
} }
} }
@ -435,13 +460,14 @@ export default class Component {
get_vars_report(): Var[] { get_vars_report(): Var[] {
const { compile_options, vars } = this; const { compile_options, vars } = this;
const vars_report = compile_options.varsReport === false const vars_report =
compile_options.varsReport === false
? [] ? []
: compile_options.varsReport === 'full' : compile_options.varsReport === 'full'
? vars ? vars
: vars.filter(v => !v.global && !v.internal); : vars.filter((v) => !v.global && !v.internal);
return vars_report.map(v => ({ return vars_report.map((v) => ({
name: v.name, name: v.name,
export_name: v.export_name || null, export_name: v.export_name || null,
injected: v.injected || false, injected: v.injected || false,
@ -505,8 +531,7 @@ export default class Component {
end, end,
pos: pos.start, pos: pos.start,
filename: this.compile_options.filename, filename: this.compile_options.filename,
toString: () => toString: () => `${warning.message} (${start.line}:${start.column})\n${frame}`
`${warning.message} (${start.line}:${start.column})\n${frame}`
}); });
} }
@ -522,7 +547,10 @@ export default class Component {
return result; return result;
} }
private _extract_exports(node: ExportDefaultDeclaration | ExportNamedDeclaration | ExportAllDeclaration, module_script: boolean) { private _extract_exports(
node: ExportDefaultDeclaration | ExportNamedDeclaration | ExportAllDeclaration,
module_script: boolean
) {
if (node.type === 'ExportDefaultDeclaration') { if (node.type === 'ExportDefaultDeclaration') {
return this.error(node as any, compiler_errors.default_export); return this.error(node as any, compiler_errors.default_export);
} }
@ -538,15 +566,25 @@ export default class Component {
} }
if (node.declaration) { if (node.declaration) {
if (node.declaration.type === 'VariableDeclaration') { if (node.declaration.type === 'VariableDeclaration') {
node.declaration.declarations.forEach(declarator => { node.declaration.declarations.forEach((declarator) => {
extract_names(declarator.id).forEach(name => { extract_names(declarator.id).forEach((name) => {
const variable = this.var_lookup.get(name); const variable = this.var_lookup.get(name);
variable.export_name = name; variable.export_name = name;
if (declarator.init?.type === 'Literal' && typeof declarator.init.value === 'boolean') { if (
declarator.init?.type === 'Literal' &&
typeof declarator.init.value === 'boolean'
) {
variable.is_boolean = true; variable.is_boolean = true;
} }
if (!module_script && variable.writable && !(variable.referenced || variable.referenced_from_script || variable.subscribable)) { if (
this.warn(declarator as any, compiler_warnings.unused_export_let(this.name.name, name)); !module_script &&
variable.writable &&
!(variable.referenced || variable.referenced_from_script || variable.subscribable)
) {
this.warn(
declarator as any,
compiler_warnings.unused_export_let(this.name.name, name)
);
} }
}); });
}); });
@ -559,14 +597,21 @@ export default class Component {
return node.declaration; return node.declaration;
} else { } else {
node.specifiers.forEach(specifier => { node.specifiers.forEach((specifier) => {
const variable = this.var_lookup.get(specifier.local.name); const variable = this.var_lookup.get(specifier.local.name);
if (variable) { if (variable) {
variable.export_name = specifier.exported.name; variable.export_name = specifier.exported.name;
if (!module_script && variable.writable && !(variable.referenced || variable.referenced_from_script || variable.subscribable)) { if (
this.warn(specifier as any, compiler_warnings.unused_export_let(this.name.name, specifier.exported.name)); !module_script &&
variable.writable &&
!(variable.referenced || variable.referenced_from_script || variable.subscribable)
) {
this.warn(
specifier as any,
compiler_warnings.unused_export_let(this.name.name, specifier.exported.name)
);
} }
} }
}); });
@ -579,7 +624,7 @@ export default class Component {
extract_javascript(script) { extract_javascript(script) {
if (!script) return null; if (!script) return null;
return script.content.body.filter(node => { return script.content.body.filter((node) => {
if (!node) return false; if (!node) return false;
if (this.hoistable_nodes.has(node)) return false; if (this.hoistable_nodes.has(node)) return false;
if (this.reactive_declaration_nodes.has(node)) return false; if (this.reactive_declaration_nodes.has(node)) return false;
@ -610,7 +655,8 @@ export default class Component {
return this.error(node as any, compiler_errors.illegal_declaration); return this.error(node as any, compiler_errors.illegal_declaration);
} }
const writable = node.type === 'VariableDeclaration' && (node.kind === 'var' || node.kind === 'let'); const writable =
node.type === 'VariableDeclaration' && (node.kind === 'var' || node.kind === 'let');
const imported = node.type.startsWith('Import'); const imported = node.type.startsWith('Import');
this.add_var(node, { this.add_var(node, {
@ -659,7 +705,7 @@ export default class Component {
if (!script) return; if (!script) return;
// inject vars for reactive declarations // inject vars for reactive declarations
script.content.body.forEach(node => { script.content.body.forEach((node) => {
if (node.type !== 'LabeledStatement') return; if (node.type !== 'LabeledStatement') return;
if (node.body.type !== 'ExpressionStatement') return; if (node.body.type !== 'ExpressionStatement') return;
@ -667,16 +713,14 @@ export default class Component {
if (expression.type !== 'AssignmentExpression') return; if (expression.type !== 'AssignmentExpression') return;
if (expression.left.type === 'MemberExpression') return; if (expression.left.type === 'MemberExpression') return;
extract_names(expression.left).forEach(name => { extract_names(expression.left).forEach((name) => {
if (!this.var_lookup.has(name) && name[0] !== '$') { if (!this.var_lookup.has(name) && name[0] !== '$') {
this.injected_reactive_declaration_vars.add(name); this.injected_reactive_declaration_vars.add(name);
} }
}); });
}); });
const { scope: instance_scope, map, globals } = create_scopes( const { scope: instance_scope, map, globals } = create_scopes(script.content);
script.content
);
this.instance_scope = instance_scope; this.instance_scope = instance_scope;
this.instance_scope_map = map; this.instance_scope_map = map;
@ -685,7 +729,8 @@ export default class Component {
return this.error(node as any, compiler_errors.illegal_declaration); return this.error(node as any, compiler_errors.illegal_declaration);
} }
const writable = node.type === 'VariableDeclaration' && (node.kind === 'var' || node.kind === 'let'); const writable =
node.type === 'VariableDeclaration' && (node.kind === 'var' || node.kind === 'let');
const imported = node.type.startsWith('Import'); const imported = node.type.startsWith('Import');
this.add_var(node, { this.add_var(node, {
@ -702,11 +747,11 @@ export default class Component {
// as `$store` will mark `store` variable as referenced and subscribable // as `$store` will mark `store` variable as referenced and subscribable
const global_keys = Array.from(globals.keys()); const global_keys = Array.from(globals.keys());
const sorted_globals = [ const sorted_globals = [
...global_keys.filter(key => key[0] !== '$'), ...global_keys.filter((key) => key[0] !== '$'),
...global_keys.filter(key => key[0] === '$') ...global_keys.filter((key) => key[0] === '$')
]; ];
sorted_globals.forEach(name => { sorted_globals.forEach((name) => {
if (this.var_lookup.has(name)) return; if (this.var_lookup.has(name)) return;
const node = globals.get(name); const node = globals.get(name);
@ -786,8 +831,8 @@ export default class Component {
walk(content, { walk(content, {
enter(node: Node, parent: Node, prop, index) { enter(node: Node, parent: Node, prop, index) {
if ((node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression')) { if (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression') {
current_function_stack.push(current_function = node); current_function_stack.push((current_function = node));
} }
if (map.has(node)) { if (map.has(node)) {
@ -801,18 +846,18 @@ export default class Component {
if (node.left.type === 'ArrayPattern') { if (node.left.type === 'ArrayPattern') {
walk(node.left, { walk(node.left, {
enter(node: Node, parent: Node) { enter(node: Node, parent: Node) {
if (node.type === 'Identifier' && if (
node.type === 'Identifier' &&
parent.type !== 'MemberExpression' && parent.type !== 'MemberExpression' &&
(parent.type !== 'AssignmentPattern' || parent.right !== node)) { (parent.type !== 'AssignmentPattern' || parent.right !== node)
) {
names.push(node.name); names.push(node.name);
} }
} }
}); });
} else { } else {
deep = node.left.type === 'MemberExpression'; deep = node.left.type === 'MemberExpression';
names = deep names = deep ? [get_object(node.left).name] : extract_names(node.left);
? [get_object(node.left).name]
: extract_names(node.left);
} }
} else if (node.type === 'UpdateExpression') { } else if (node.type === 'UpdateExpression') {
deep = node.argument.type === 'MemberExpression'; deep = node.argument.type === 'MemberExpression';
@ -820,7 +865,7 @@ export default class Component {
names.push(name); names.push(name);
} }
if (names.length > 0) { if (names.length > 0) {
names.forEach(name => { names.forEach((name) => {
let current_scope = scope; let current_scope = scope;
let declaration; let declaration;
@ -863,14 +908,22 @@ export default class Component {
}, },
leave(node: Node) { leave(node: Node) {
if ((node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression')) { if (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression') {
current_function_stack.pop(); current_function_stack.pop();
current_function = current_function_stack[current_function_stack.length - 1]; current_function = current_function_stack[current_function_stack.length - 1];
} }
// do it on leave, to prevent infinite loop // do it on leave, to prevent infinite loop
if (component.compile_options.dev && component.compile_options.loopGuardTimeout > 0 && (!current_function || (!current_function.generator && !current_function.async))) { if (
const to_replace_for_loop_protect = component.loop_protect(node, scope, component.compile_options.loopGuardTimeout); component.compile_options.dev &&
component.compile_options.loopGuardTimeout > 0 &&
(!current_function || (!current_function.generator && !current_function.async))
) {
const to_replace_for_loop_protect = component.loop_protect(
node,
scope,
component.compile_options.loopGuardTimeout
);
if (to_replace_for_loop_protect) { if (to_replace_for_loop_protect) {
this.replace(to_replace_for_loop_protect); this.replace(to_replace_for_loop_protect);
scope_updated = true; scope_updated = true;
@ -922,7 +975,7 @@ export default class Component {
const deep = assignee.type === 'MemberExpression'; const deep = assignee.type === 'MemberExpression';
names.forEach(name => { names.forEach((name) => {
const scope_owner = scope.find_owner(name); const scope_owner = scope.find_owner(name);
if ( if (
scope_owner !== null scope_owner !== null
@ -952,12 +1005,13 @@ export default class Component {
}); });
} }
warn_on_undefined_store_value_references(node: Node, parent: Node, prop: string | number | symbol, scope: Scope) { warn_on_undefined_store_value_references(
if ( node: Node,
node.type === 'LabeledStatement' && parent: Node,
node.label.name === '$' && prop: string | number | symbol,
parent.type !== 'Program' scope: Scope
) { ) {
if (node.type === 'LabeledStatement' && node.label.name === '$' && parent.type !== 'Program') {
this.warn(node as any, compiler_warnings.non_top_level_reactive_declaration); this.warn(node as any, compiler_warnings.non_top_level_reactive_declaration);
} }
@ -970,8 +1024,17 @@ export default class Component {
this.warn_if_undefined(name, object, null); this.warn_if_undefined(name, object, null);
} }
if (name[1] !== '$' && scope.has(name.slice(1)) && scope.find_owner(name.slice(1)) !== this.instance_scope) { if (
if (!((regex_contains_term_function.test(parent.type) && prop === 'params') || (parent.type === 'VariableDeclarator' && prop === 'id'))) { name[1] !== '$' &&
scope.has(name.slice(1)) &&
scope.find_owner(name.slice(1)) !== this.instance_scope
) {
if (
!(
(regex_contains_term_function.test(parent.type) && prop === 'params') ||
(parent.type === 'VariableDeclarator' && prop === 'id')
)
) {
return this.error(node as any, compiler_errors.contextual_store); return this.error(node as any, compiler_errors.contextual_store);
} }
} }
@ -980,9 +1043,11 @@ export default class Component {
} }
loop_protect(node, scope: Scope, timeout: number): Node | null { loop_protect(node, scope: Scope, timeout: number): Node | null {
if (node.type === 'WhileStatement' || if (
node.type === 'WhileStatement' ||
node.type === 'ForStatement' || node.type === 'ForStatement' ||
node.type === 'DoWhileStatement') { node.type === 'DoWhileStatement'
) {
const guard = this.get_unique_name('guard', scope); const guard = this.get_unique_name('guard', scope);
this.used_names.add(guard.name); this.used_names.add(guard.name);
@ -1000,10 +1065,7 @@ export default class Component {
return { return {
type: 'BlockStatement', type: 'BlockStatement',
body: [ body: [before[0], node]
before[0],
node
]
}; };
} }
return null; return null;
@ -1036,7 +1098,11 @@ export default class Component {
const inserts = []; const inserts = [];
const props = []; const props = [];
function add_new_props(exported: Identifier, local: Pattern, default_value: Expression) { function add_new_props(
exported: Identifier,
local: Pattern,
default_value: Expression
) {
props.push({ props.push({
type: 'Property', type: 'Property',
method: false, method: false,
@ -1074,7 +1140,11 @@ export default class Component {
if (variable.export_name && variable.writable) { if (variable.export_name && variable.writable) {
const alias_name = component.get_unique_name(local.name); const alias_name = component.get_unique_name(local.name);
add_new_props({ type: 'Identifier', name: variable.export_name }, local, alias_name); add_new_props(
{ type: 'Identifier', name: variable.export_name },
local,
alias_name
);
return alias_name; return alias_name;
} }
return local; return local;
@ -1097,7 +1167,11 @@ export default class Component {
break; break;
} }
case 'ArrayPattern': { case 'ArrayPattern': {
const handle_element = (element: Pattern | null, index: number, array: Array<Pattern | null>) => { const handle_element = (
element: Pattern | null,
index: number,
array: Array<Pattern | null>
) => {
if (element) { if (element) {
if (element.type === 'Identifier') { if (element.type === 'Identifier') {
array[index] = get_new_name(element); array[index] = get_new_name(element);
@ -1135,7 +1209,11 @@ export default class Component {
const variable = component.var_lookup.get(name); const variable = component.var_lookup.get(name);
const is_props = variable.export_name && variable.writable; const is_props = variable.export_name && variable.writable;
if (is_props) { if (is_props) {
add_new_props({ type: 'Identifier', name: variable.export_name }, declarator.id, declarator.init); add_new_props(
{ type: 'Identifier', name: variable.export_name },
declarator.id,
declarator.init
);
node.declarations.splice(index--, 1); node.declarations.splice(index--, 1);
} }
if (variable.subscribable && (is_props || declarator.init)) { if (variable.subscribable && (is_props || declarator.init)) {
@ -1144,11 +1222,13 @@ export default class Component {
} }
} }
this.replace(b` this.replace(
b`
${node.declarations.length ? node : null} ${node.declarations.length ? node : null}
${props.length > 0 && b`let { ${props} } = $$props;`} ${props.length > 0 && b`let { ${props} } = $$props;`}
${inserts} ${inserts}
` as any); ` as any
);
return this.skip(); return this.skip();
} }
} }
@ -1168,12 +1248,7 @@ export default class Component {
// reference instance variables other than other // reference instance variables other than other
// hoistable functions. TODO others? // hoistable functions. TODO others?
const { const { hoistable_nodes, var_lookup, injected_reactive_declaration_vars, imports } = this;
hoistable_nodes,
var_lookup,
injected_reactive_declaration_vars,
imports
} = this;
const top_level_function_declarations = new Map(); const top_level_function_declarations = new Map();
@ -1183,7 +1258,7 @@ export default class Component {
const node = body[i]; const node = body[i];
if (node.type === 'VariableDeclaration') { if (node.type === 'VariableDeclaration') {
const all_hoistable = node.declarations.every(d => { const all_hoistable = node.declarations.every((d) => {
if (!d.init) return false; if (!d.init) return false;
if (d.init.type !== 'Literal') return false; if (d.init.type !== 'Literal') return false;
@ -1198,11 +1273,7 @@ export default class Component {
if (v.export_name) return false; if (v.export_name) return false;
if (this.var_lookup.get(name).reassigned) return false; if (this.var_lookup.get(name).reassigned) return false;
if ( if (this.vars.find((variable) => variable.name === name && variable.module)) {
this.vars.find(
variable => variable.name === name && variable.module
)
) {
return false; return false;
} }
@ -1210,7 +1281,7 @@ export default class Component {
}); });
if (all_hoistable) { if (all_hoistable) {
node.declarations.forEach(d => { node.declarations.forEach((d) => {
const variable = this.var_lookup.get((d.id as Identifier).name); const variable = this.var_lookup.get((d.id as Identifier).name);
variable.hoistable = true; variable.hoistable = true;
}); });
@ -1238,7 +1309,7 @@ export default class Component {
const checked = new Set(); const checked = new Set();
const walking = new Set(); const walking = new Set();
const is_hoistable = fn_declaration => { const is_hoistable = (fn_declaration) => {
if (fn_declaration.type === 'ExportNamedDeclaration') { if (fn_declaration.type === 'ExportNamedDeclaration') {
fn_declaration = fn_declaration.declaration; fn_declaration = fn_declaration.declaration;
} }
@ -1260,7 +1331,9 @@ export default class Component {
scope = map.get(node); scope = map.get(node);
} }
if (is_reference(node as NodeWithPropertyDefinition, parent as NodeWithPropertyDefinition)) { if (
is_reference(node as NodeWithPropertyDefinition, parent as NodeWithPropertyDefinition)
) {
const { name } = flatten_reference(node); const { name } = flatten_reference(node);
const owner = scope.find_owner(name); const owner = scope.find_owner(name);
@ -1278,9 +1351,7 @@ export default class Component {
if (variable.hoistable) return; if (variable.hoistable) return;
if (top_level_function_declarations.has(name)) { if (top_level_function_declarations.has(name)) {
const other_declaration = top_level_function_declarations.get( const other_declaration = top_level_function_declarations.get(name);
name
);
if (walking.has(other_declaration)) { if (walking.has(other_declaration)) {
hoistable = false; hoistable = false;
@ -1347,7 +1418,7 @@ export default class Component {
declaration: Node; declaration: Node;
}> = []; }> = [];
this.ast.instance.content.body.forEach(node => { this.ast.instance.content.body.forEach((node) => {
const ignores = extract_svelte_ignore_from_comments(node); const ignores = extract_svelte_ignore_from_comments(node);
if (ignores.length) this.push_ignores(ignores); if (ignores.length) this.push_ignores(ignores);
@ -1384,7 +1455,7 @@ export default class Component {
if (node.type === 'AssignmentExpression') { if (node.type === 'AssignmentExpression') {
const left = get_object(node.left); const left = get_object(node.left);
extract_identifiers(left).forEach(node => { extract_identifiers(left).forEach((node) => {
assignee_nodes.add(node); assignee_nodes.add(node);
assignees.add(node.name); assignees.add(node.name);
}); });
@ -1395,7 +1466,9 @@ export default class Component {
} else if (node.type === 'UpdateExpression') { } else if (node.type === 'UpdateExpression') {
const identifier = get_object(node.argument); const identifier = get_object(node.argument);
assignees.add(identifier.name); assignees.add(identifier.name);
} else if (is_reference(node as NodeWithPropertyDefinition, parent as NodeWithPropertyDefinition)) { } else if (
is_reference(node as NodeWithPropertyDefinition, parent as NodeWithPropertyDefinition)
) {
const identifier = get_object(node); const identifier = get_object(node);
if (!assignee_nodes.has(identifier)) { if (!assignee_nodes.has(identifier)) {
const { name } = identifier; const { name } = identifier;
@ -1410,8 +1483,7 @@ export default class Component {
module_dependencies.add(name); module_dependencies.add(name);
} }
} }
const is_writable_or_mutated = const is_writable_or_mutated = variable && (variable.writable || variable.mutated);
variable && (variable.writable || variable.mutated);
if ( if (
should_add_as_dependency && should_add_as_dependency &&
(!owner || owner === component.instance_scope) && (!owner || owner === component.instance_scope) &&
@ -1433,7 +1505,12 @@ export default class Component {
}); });
if (module_dependencies.size > 0 && dependencies.size === 0) { if (module_dependencies.size > 0 && dependencies.size === 0) {
component.warn(node.body as any, compiler_warnings.module_script_variable_reactive_declaration(Array.from(module_dependencies))); component.warn(
node.body as any,
compiler_warnings.module_script_variable_reactive_declaration(
Array.from(module_dependencies)
)
);
} }
const { expression } = node.body as ExpressionStatement; const { expression } = node.body as ExpressionStatement;
@ -1452,8 +1529,8 @@ export default class Component {
const lookup = new Map(); const lookup = new Map();
unsorted_reactive_declarations.forEach(declaration => { unsorted_reactive_declarations.forEach((declaration) => {
declaration.assignees.forEach(name => { declaration.assignees.forEach((name) => {
if (!lookup.has(name)) { if (!lookup.has(name)) {
lookup.set(name, []); lookup.set(name, []);
} }
@ -1464,16 +1541,18 @@ export default class Component {
}); });
}); });
const cycle = check_graph_for_cycles(unsorted_reactive_declarations.reduce((acc, declaration) => { const cycle = check_graph_for_cycles(
declaration.assignees.forEach(v => { unsorted_reactive_declarations.reduce((acc, declaration) => {
declaration.dependencies.forEach(w => { declaration.assignees.forEach((v) => {
declaration.dependencies.forEach((w) => {
if (!declaration.assignees.has(w)) { if (!declaration.assignees.has(w)) {
acc.push([v, w]); acc.push([v, w]);
} }
}); });
}); });
return acc; return acc;
}, [])); }, [])
);
if (cycle && cycle.length) { if (cycle && cycle.length) {
const declarationList = lookup.get(cycle[0]); const declarationList = lookup.get(cycle[0]);
@ -1481,10 +1560,10 @@ export default class Component {
return this.error(declaration.node, compiler_errors.cyclical_reactive_declaration(cycle)); return this.error(declaration.node, compiler_errors.cyclical_reactive_declaration(cycle));
} }
const add_declaration = declaration => { const add_declaration = (declaration) => {
if (this.reactive_declarations.includes(declaration)) return; if (this.reactive_declarations.includes(declaration)) return;
declaration.dependencies.forEach(name => { declaration.dependencies.forEach((name) => {
if (declaration.assignees.has(name)) return; if (declaration.assignees.has(name)) return;
const earlier_declarations = lookup.get(name); const earlier_declarations = lookup.get(name);
if (earlier_declarations) { if (earlier_declarations) {
@ -1499,14 +1578,14 @@ export default class Component {
} }
check_if_tags_content_dynamic() { check_if_tags_content_dynamic() {
this.tags.forEach(tag => { this.tags.forEach((tag) => {
tag.check_if_content_dynamic(); tag.check_if_content_dynamic();
}); });
} }
warn_if_undefined(name: string, node, template_scope: TemplateScope) { warn_if_undefined(name: string, node, template_scope: TemplateScope) {
if (name[0] === '$') { if (name[0] === '$') {
if (name === '$' || name[1] === '$' && !is_reserved_keyword(name)) { if (name === '$' || (name[1] === '$' && !is_reserved_keyword(name))) {
return this.error(node, compiler_errors.illegal_global(name)); return this.error(node, compiler_errors.illegal_global(name));
} }
@ -1549,7 +1628,7 @@ function process_component_options(component: Component, nodes) {
namespace: component.compile_options.namespace namespace: component.compile_options.namespace
}; };
const node = nodes.find(node => node.name === 'svelte:options'); const node = nodes.find((node) => node.name === 'svelte:options');
function get_value(attribute, { code, message }) { function get_value(attribute, { code, message }) {
const { value } = attribute; const { value } = attribute;
@ -1571,7 +1650,7 @@ function process_component_options(component: Component, nodes) {
} }
if (node) { if (node) {
node.attributes.forEach(attribute => { node.attributes.forEach((attribute) => {
if (attribute.type === 'Attribute') { if (attribute.type === 'Attribute') {
const { name } = attribute; const { name } = attribute;
@ -1588,7 +1667,7 @@ function process_component_options(component: Component, nodes) {
component.warn(attribute, compiler_warnings.missing_custom_element_compile_options); component.warn(attribute, compiler_warnings.missing_custom_element_compile_options);
} }
component_options.customElement = component_options.customElement || {} as any; component_options.customElement = component_options.customElement || ({} as any);
component_options.customElement.tag = tag; component_options.customElement.tag = tag;
} }
@ -1600,7 +1679,7 @@ function process_component_options(component: Component, nodes) {
} }
case 'customElement': { case 'customElement': {
component_options.customElement = component_options.customElement || {} as any; component_options.customElement = component_options.customElement || ({} as any);
const { value } = attribute; const { value } = attribute;
@ -1614,9 +1693,7 @@ function process_component_options(component: Component, nodes) {
return component.error(attribute, compiler_errors.invalid_customElement_attribute); return component.error(attribute, compiler_errors.invalid_customElement_attribute);
} }
const tag = value[0].expression.properties.find( const tag = value[0].expression.properties.find((prop: any) => prop.key.name === 'tag');
(prop: any) => prop.key.name === 'tag'
);
if (tag) { if (tag) {
parse_tag(tag, tag.value?.value); parse_tag(tag, tag.value?.value);
} else { } else {
@ -1627,7 +1704,8 @@ function process_component_options(component: Component, nodes) {
(prop: any) => prop.key.name === 'props' (prop: any) => prop.key.name === 'props'
); );
if (props) { if (props) {
const error = () => component.error(attribute, compiler_errors.invalid_props_attribute); const error = () =>
component.error(attribute, compiler_errors.invalid_props_attribute);
if (props.value?.type !== 'ObjectExpression') { if (props.value?.type !== 'ObjectExpression') {
return error(); return error();
} }
@ -1635,22 +1713,37 @@ function process_component_options(component: Component, nodes) {
component_options.customElement.props = {}; component_options.customElement.props = {};
for (const property of (props.value as ObjectExpression).properties) { for (const property of (props.value as ObjectExpression).properties) {
if (property.type !== 'Property' || property.computed || property.key.type !== 'Identifier' || property.value.type !== 'ObjectExpression') { if (
property.type !== 'Property' ||
property.computed ||
property.key.type !== 'Identifier' ||
property.value.type !== 'ObjectExpression'
) {
return error(); return error();
} }
component_options.customElement.props[property.key.name] = {}; component_options.customElement.props[property.key.name] = {};
for (const prop of property.value.properties) { for (const prop of property.value.properties) {
if (prop.type !== 'Property' || prop.computed || prop.key.type !== 'Identifier' || prop.value.type !== 'Literal') { if (
prop.type !== 'Property' ||
prop.computed ||
prop.key.type !== 'Identifier' ||
prop.value.type !== 'Literal'
) {
return error(); return error();
} }
if (['reflect', 'attribute', 'type'].indexOf(prop.key.name) === -1 || if (
prop.key.name === 'type' && ['String', 'Number', 'Boolean', 'Array', 'Object'].indexOf(prop.value.value as string) === -1 || ['reflect', 'attribute', 'type'].indexOf(prop.key.name) === -1 ||
prop.key.name === 'reflect' && typeof prop.value.value !== 'boolean' || (prop.key.name === 'type' &&
prop.key.name === 'attribute' && typeof prop.value.value !== 'string' ['String', 'Number', 'Boolean', 'Array', 'Object'].indexOf(
prop.value.value as string
) === -1) ||
(prop.key.name === 'reflect' && typeof prop.value.value !== 'boolean') ||
(prop.key.name === 'attribute' && typeof prop.value.value !== 'string')
) { ) {
return error(); return error();
} }
component_options.customElement.props[property.key.name][prop.key.name] = prop.value.value; component_options.customElement.props[property.key.name][prop.key.name] =
prop.value.value;
} }
} }
} }
@ -1680,7 +1773,10 @@ function process_component_options(component: Component, nodes) {
if (valid_namespaces.indexOf(ns) === -1) { if (valid_namespaces.indexOf(ns) === -1) {
const match = fuzzymatch(ns, valid_namespaces); const match = fuzzymatch(ns, valid_namespaces);
return component.error(attribute, compiler_errors.invalid_namespace_property(ns, match)); return component.error(
attribute,
compiler_errors.invalid_namespace_property(ns, match)
);
} }
component_options.namespace = ns; component_options.namespace = ns;
@ -1701,7 +1797,10 @@ function process_component_options(component: Component, nodes) {
} }
default: default:
return component.error(attribute, compiler_errors.invalid_options_attribute_unknown(name)); return component.error(
attribute,
compiler_errors.invalid_options_attribute_unknown(name)
);
} }
} else { } else {
return component.error(attribute, compiler_errors.invalid_options_attribute); return component.error(attribute, compiler_errors.invalid_options_attribute);

@ -22,7 +22,9 @@ export default {
}), }),
invalid_binding_no_checkbox: (binding: string, is_radio: boolean) => ({ invalid_binding_no_checkbox: (binding: string, is_radio: boolean) => ({
code: 'invalid-binding', code: 'invalid-binding',
message: `'${binding}' binding can only be used with <input type="checkbox">` + (is_radio ? ' — for <input type="radio">, use \'group\' binding' : '') message:
`'${binding}' binding can only be used with <input type="checkbox">` +
(is_radio ? ' — for <input type="radio">, use \'group\' binding' : '')
}), }),
invalid_binding: (binding: string) => ({ invalid_binding: (binding: string) => ({
code: 'invalid-binding', code: 'invalid-binding',
@ -30,7 +32,9 @@ export default {
}), }),
invalid_binding_window: (parts: string[]) => ({ invalid_binding_window: (parts: string[]) => ({
code: 'invalid-binding', code: 'invalid-binding',
message: `Bindings on <svelte:window> must be to top-level properties, e.g. '${parts[parts.length - 1]}' rather than '${parts.join('.')}'` message: `Bindings on <svelte:window> must be to top-level properties, e.g. '${
parts[parts.length - 1]
}' rather than '${parts.join('.')}'`
}), }),
invalid_binding_let: { invalid_binding_let: {
code: 'invalid-binding', code: 'invalid-binding',
@ -54,23 +58,24 @@ export default {
}), }),
invalid_type: { invalid_type: {
code: 'invalid-type', code: 'invalid-type',
message: '\'type\' attribute cannot be dynamic if input uses two-way binding' message: "'type' attribute cannot be dynamic if input uses two-way binding"
}, },
missing_type: { missing_type: {
code: 'missing-type', code: 'missing-type',
message: '\'type\' attribute must be specified' message: "'type' attribute must be specified"
}, },
dynamic_multiple_attribute: { dynamic_multiple_attribute: {
code: 'dynamic-multiple-attribute', code: 'dynamic-multiple-attribute',
message: '\'multiple\' attribute cannot be dynamic if select uses two-way binding' message: "'multiple' attribute cannot be dynamic if select uses two-way binding"
}, },
missing_contenteditable_attribute: { missing_contenteditable_attribute: {
code: 'missing-contenteditable-attribute', code: 'missing-contenteditable-attribute',
message: '\'contenteditable\' attribute is required for textContent, innerHTML and innerText two-way bindings' message:
"'contenteditable' attribute is required for textContent, innerHTML and innerText two-way bindings"
}, },
dynamic_contenteditable_attribute: { dynamic_contenteditable_attribute: {
code: 'dynamic-contenteditable-attribute', code: 'dynamic-contenteditable-attribute',
message: '\'contenteditable\' attribute cannot be dynamic if element uses two-way binding' message: "'contenteditable' attribute cannot be dynamic if element uses two-way binding"
}, },
invalid_event_modifier_combination: (modifier1: string, modifier2: string) => ({ invalid_event_modifier_combination: (modifier1: string, modifier2: string) => ({
code: 'invalid-event-modifier', code: 'invalid-event-modifier',
@ -90,7 +95,8 @@ export default {
}, },
textarea_duplicate_value: { textarea_duplicate_value: {
code: 'textarea-duplicate-value', code: 'textarea-duplicate-value',
message: 'A <textarea> can have either a value attribute or (equivalently) child content, but not both' message:
'A <textarea> can have either a value attribute or (equivalently) child content, but not both'
}, },
illegal_attribute: (name: string) => ({ illegal_attribute: (name: string) => ({
code: 'illegal-attribute', code: 'illegal-attribute',
@ -106,7 +112,8 @@ export default {
}), }),
invalid_slotted_content: { invalid_slotted_content: {
code: 'invalid-slotted-content', code: 'invalid-slotted-content',
message: 'Element with a slot=\'...\' attribute must be a child of a component or a descendant of a custom element' message:
"Element with a slot='...' attribute must be a child of a component or a descendant of a custom element"
}, },
invalid_attribute_head: { invalid_attribute_head: {
code: 'invalid-attribute', code: 'invalid-attribute',
@ -158,13 +165,14 @@ export default {
}, },
duplicate_transition: (directive: string, parent_directive: string) => { duplicate_transition: (directive: string, parent_directive: string) => {
function describe(_directive: string) { function describe(_directive: string) {
return _directive === 'transition' return _directive === 'transition' ? "a 'transition'" : `an '${_directive}'`;
? "a 'transition'"
: `an '${_directive}'`;
} }
const message = directive === parent_directive const message =
directive === parent_directive
? `An element can only have one '${directive}' directive` ? `An element can only have one '${directive}' directive`
: `An element cannot have both ${describe(parent_directive)} directive and ${describe(directive)} directive`; : `An element cannot have both ${describe(parent_directive)} directive and ${describe(
directive
)} directive`;
return { return {
code: 'duplicate-transition', code: 'duplicate-transition',
message message
@ -172,7 +180,8 @@ export default {
}, },
contextual_store: { contextual_store: {
code: 'contextual-store', code: 'contextual-store',
message: 'Stores must be declared at the top level of the component (this may change in a future version of Svelte)' message:
'Stores must be declared at the top level of the component (this may change in a future version of Svelte)'
}, },
default_export: { default_export: {
code: 'default-export', code: 'default-export',
@ -204,7 +213,8 @@ export default {
}, },
invalid_customElement_attribute: { invalid_customElement_attribute: {
code: 'invalid-customElement-attribute', code: 'invalid-customElement-attribute',
message: "'customElement' must be a string literal defining a valid custom element name or an object of the form " + message:
"'customElement' must be a string literal defining a valid custom element name or an object of the form " +
"{ tag: string; shadow?: 'open' | 'none'; props?: { [key: string]: { attribute?: string; reflect?: boolean; type: .. } } }" "{ tag: string; shadow?: 'open' | 'none'; props?: { [key: string]: { attribute?: string; reflect?: boolean; type: .. } } }"
}, },
invalid_tag_attribute: { invalid_tag_attribute: {
@ -217,12 +227,14 @@ export default {
}, },
invalid_props_attribute: { invalid_props_attribute: {
code: 'invalid-props-attribute', code: 'invalid-props-attribute',
message: "'props' must be a statically analyzable object literal of the form " + message:
"'props' must be a statically analyzable object literal of the form " +
"'{ [key: string]: { attribute?: string; reflect?: boolean; type?: 'String' | 'Boolean' | 'Number' | 'Array' | 'Object' }'" "'{ [key: string]: { attribute?: string; reflect?: boolean; type?: 'String' | 'Boolean' | 'Number' | 'Array' | 'Object' }'"
}, },
invalid_namespace_property: (namespace: string, suggestion?: string) => ({ invalid_namespace_property: (namespace: string, suggestion?: string) => ({
code: 'invalid-namespace-property', code: 'invalid-namespace-property',
message: `Invalid namespace '${namespace}'` + (suggestion ? ` (did you mean '${suggestion}'?)` : '') message:
`Invalid namespace '${namespace}'` + (suggestion ? ` (did you mean '${suggestion}'?)` : '')
}), }),
invalid_namespace_attribute: { invalid_namespace_attribute: {
code: 'invalid-namespace-attribute', code: 'invalid-namespace-attribute',
@ -238,7 +250,8 @@ export default {
}), }),
invalid_options_attribute: { invalid_options_attribute: {
code: 'invalid-options-attribute', code: 'invalid-options-attribute',
message: "<svelte:options> can only have static 'tag', 'namespace', 'accessors', 'immutable' and 'preserveWhitespace' attributes" message:
"<svelte:options> can only have static 'tag', 'namespace', 'accessors', 'immutable' and 'preserveWhitespace' attributes"
}, },
css_invalid_global: { css_invalid_global: {
code: 'css-invalid-global', code: 'css-invalid-global',
@ -250,7 +263,8 @@ export default {
}, },
css_invalid_global_selector_position: { css_invalid_global_selector_position: {
code: 'css-invalid-global-selector-position', code: 'css-invalid-global-selector-position',
message: ':global(...) not at the start of a selector sequence should not contain type or universal selectors' message:
':global(...) not at the start of a selector sequence should not contain type or universal selectors'
}, },
css_invalid_selector: (selector: string) => ({ css_invalid_selector: (selector: string) => ({
code: 'css-invalid-selector', code: 'css-invalid-selector',
@ -262,15 +276,18 @@ export default {
}, },
invalid_animation_immediate: { invalid_animation_immediate: {
code: 'invalid-animation', code: 'invalid-animation',
message: 'An element that uses the animate directive must be the immediate child of a keyed each block' message:
'An element that uses the animate directive must be the immediate child of a keyed each block'
}, },
invalid_animation_key: { invalid_animation_key: {
code: 'invalid-animation', code: 'invalid-animation',
message: 'An element that uses the animate directive must be used inside a keyed each block. Did you forget to add a key to your each block?' message:
'An element that uses the animate directive must be used inside a keyed each block. Did you forget to add a key to your each block?'
}, },
invalid_animation_sole: { invalid_animation_sole: {
code: 'invalid-animation', code: 'invalid-animation',
message: 'An element that uses the animate directive must be the sole child of a keyed each block' message:
'An element that uses the animate directive must be the sole child of a keyed each block'
}, },
invalid_animation_dynamic_element: { invalid_animation_dynamic_element: {
code: 'invalid-animation', code: 'invalid-animation',
@ -278,11 +295,13 @@ export default {
}, },
invalid_directive_value: { invalid_directive_value: {
code: 'invalid-directive-value', code: 'invalid-directive-value',
message: 'Can only bind to an identifier (e.g. `foo`) or a member expression (e.g. `foo.bar` or `foo[baz]`)' message:
'Can only bind to an identifier (e.g. `foo`) or a member expression (e.g. `foo.bar` or `foo[baz]`)'
}, },
invalid_const_placement: { invalid_const_placement: {
code: 'invalid-const-placement', code: 'invalid-const-placement',
message: '{@const} must be the immediate child of {#if}, {:else if}, {:else}, {#each}, {:then}, {:catch}, <svelte:fragment> or <Component>' message:
'{@const} must be the immediate child of {#if}, {:else if}, {:else}, {#each}, {:then}, {:catch}, <svelte:fragment> or <Component>'
}, },
invalid_const_declaration: (name: string) => ({ invalid_const_declaration: (name: string) => ({
code: 'invalid-const-declaration', code: 'invalid-const-declaration',

@ -24,15 +24,22 @@ export default {
}, },
module_script_variable_reactive_declaration: (names: string[]) => ({ module_script_variable_reactive_declaration: (names: string[]) => ({
code: 'module-script-reactive-declaration', code: 'module-script-reactive-declaration',
message: `${names.map(name => `"${name}"`).join(', ')} ${names.length > 1 ? 'are' : 'is'} declared in a module script and will not be reactive` message: `${names.map((name) => `"${name}"`).join(', ')} ${
names.length > 1 ? 'are' : 'is'
} declared in a module script and will not be reactive`
}), }),
missing_declaration: (name: string, has_script: boolean) => ({ missing_declaration: (name: string, has_script: boolean) => ({
code: 'missing-declaration', code: 'missing-declaration',
message: `'${name}' is not defined` + (has_script ? '' : `. Consider adding a <script> block with 'export let ${name}' to declare a prop`) message:
`'${name}' is not defined` +
(has_script
? ''
: `. Consider adding a <script> block with 'export let ${name}' to declare a prop`)
}), }),
missing_custom_element_compile_options: { missing_custom_element_compile_options: {
code: 'missing-custom-element-compile-options', code: 'missing-custom-element-compile-options',
message: "The 'customElement' option is used when generating a custom element. Did you forget the 'customElement: true' compile option?" message:
"The 'customElement' option is used when generating a custom element. Did you forget the 'customElement: true' compile option?"
}, },
css_unused_selector: (selector: string) => ({ css_unused_selector: (selector: string) => ({
code: 'css-unused-selector', code: 'css-unused-selector',
@ -52,7 +59,7 @@ export default {
}), }),
avoid_is: { avoid_is: {
code: 'avoid-is', code: 'avoid-is',
message: 'The \'is\' attribute is not supported cross-browser and should be avoided' message: "The 'is' attribute is not supported cross-browser and should be avoided"
}, },
invalid_html_attribute: (name: string, suggestion: string) => ({ invalid_html_attribute: (name: string, suggestion: string) => ({
code: 'invalid-html-attribute', code: 'invalid-html-attribute',
@ -78,10 +85,14 @@ export default {
message = `The value of '${attribute}' must be exactly one of true, false, or mixed`; message = `The value of '${attribute}' must be exactly one of true, false, or mixed`;
break; break;
case 'token': case 'token':
message = `The value of '${attribute}' must be exactly one of ${(schema.values || []).join(', ')}`; message = `The value of '${attribute}' must be exactly one of ${(schema.values || []).join(
', '
)}`;
break; break;
case 'tokenlist': case 'tokenlist':
message = `The value of '${attribute}' must be a space-separated list of one or more of ${(schema.values || []).join(', ')}`; message = `The value of '${attribute}' must be a space-separated list of one or more of ${(
schema.values || []
).join(', ')}`;
break; break;
default: default:
message = `The value of '${attribute}' must be of type ${schema.type}`; message = `The value of '${attribute}' must be of type ${schema.type}`;
@ -93,7 +104,9 @@ export default {
}, },
a11y_unknown_aria_attribute: (attribute: string, suggestion?: string) => ({ a11y_unknown_aria_attribute: (attribute: string, suggestion?: string) => ({
code: 'a11y-unknown-aria-attribute', code: 'a11y-unknown-aria-attribute',
message: `A11y: Unknown aria attribute 'aria-${attribute}'` + (suggestion ? ` (did you mean '${suggestion}'?)` : '') message:
`A11y: Unknown aria attribute 'aria-${attribute}'` +
(suggestion ? ` (did you mean '${suggestion}'?)` : '')
}), }),
a11y_hidden: (name: string) => ({ a11y_hidden: (name: string) => ({
code: 'a11y-hidden', code: 'a11y-hidden',
@ -117,9 +130,14 @@ export default {
}), }),
a11y_no_static_element_interactions: (element: string, handlers: string[]) => ({ a11y_no_static_element_interactions: (element: string, handlers: string[]) => ({
code: 'a11y-no-static-element-interactions', code: 'a11y-no-static-element-interactions',
message: `A11y: <${element}> with ${handlers.join(', ')} ${handlers.length === 1 ? 'handler' : 'handlers'} must have an ARIA role` message: `A11y: <${element}> with ${handlers.join(', ')} ${
handlers.length === 1 ? 'handler' : 'handlers'
} must have an ARIA role`
}), }),
a11y_no_interactive_element_to_noninteractive_role: (role: string | boolean, element: string) => ({ a11y_no_interactive_element_to_noninteractive_role: (
role: string | boolean,
element: string
) => ({
code: 'a11y-no-interactive-element-to-noninteractive-role', code: 'a11y-no-interactive-element-to-noninteractive-role',
message: `A11y: <${element}> cannot have role '${role}'` message: `A11y: <${element}> cannot have role '${role}'`
}), }),
@ -127,15 +145,25 @@ export default {
code: 'a11y-no-noninteractive-element-interactions', code: 'a11y-no-noninteractive-element-interactions',
message: `A11y: Non-interactive element <${element}> should not be assigned mouse or keyboard event listeners.` message: `A11y: Non-interactive element <${element}> should not be assigned mouse or keyboard event listeners.`
}), }),
a11y_no_noninteractive_element_to_interactive_role: (role: string | boolean, element: string) => ({ a11y_no_noninteractive_element_to_interactive_role: (
role: string | boolean,
element: string
) => ({
code: 'a11y-no-noninteractive-element-to-interactive-role', code: 'a11y-no-noninteractive-element-to-interactive-role',
message: `A11y: Non-interactive element <${element}> cannot have interactive role '${role}'` message: `A11y: Non-interactive element <${element}> cannot have interactive role '${role}'`
}), }),
a11y_role_has_required_aria_props: (role: string, props: string[]) => ({ a11y_role_has_required_aria_props: (role: string, props: string[]) => ({
code: 'a11y-role-has-required-aria-props', code: 'a11y-role-has-required-aria-props',
message: `A11y: Elements with the ARIA role "${role}" must have the following attributes defined: ${props.map(name => `"${name}"`).join(', ')}` message: `A11y: Elements with the ARIA role "${role}" must have the following attributes defined: ${props
.map((name) => `"${name}"`)
.join(', ')}`
}), }),
a11y_role_supports_aria_props: (attribute: string, role: string, is_implicit: boolean, name: string) => { a11y_role_supports_aria_props: (
attribute: string,
role: string,
is_implicit: boolean,
name: string
) => {
let message = `The attribute '${attribute}' is not supported by the role '${role}'.`; let message = `The attribute '${attribute}' is not supported by the role '${role}'.`;
if (is_implicit) { if (is_implicit) {
message += ` This role is implicit on the element <${name}>.`; message += ` This role is implicit on the element <${name}>.`;
@ -172,7 +200,9 @@ export default {
}), }),
a11y_autocomplete_valid: (type: null | true | string, value: null | true | string) => ({ a11y_autocomplete_valid: (type: null | true | string, value: null | true | string) => ({
code: 'a11y-autocomplete-valid', code: 'a11y-autocomplete-valid',
message: `A11y: The value '${value}' is not supported by the attribute 'autocomplete' on element <input type="${type || '...'}">` message: `A11y: The value '${value}' is not supported by the attribute 'autocomplete' on element <input type="${
type || '...'
}">`
}), }),
a11y_img_redundant_alt: { a11y_img_redundant_alt: {
code: 'a11y-img-redundant-alt', code: 'a11y-img-redundant-alt',
@ -208,7 +238,8 @@ export default {
}), }),
a11y_click_events_have_key_events: { a11y_click_events_have_key_events: {
code: 'a11y-click-events-have-key-events', code: 'a11y-click-events-have-key-events',
message: 'A11y: visible, non-interactive elements with an on:click event must be accompanied by an on:keydown, on:keyup, or on:keypress event.' message:
'A11y: visible, non-interactive elements with an on:click event must be accompanied by an on:keydown, on:keyup, or on:keypress event.'
}, },
a11y_missing_content: (name: string) => ({ a11y_missing_content: (name: string) => ({
code: 'a11y-missing-content', code: 'a11y-missing-content',
@ -224,7 +255,7 @@ export default {
}, },
redundant_event_modifier_for_touch: { redundant_event_modifier_for_touch: {
code: 'redundant-event-modifier', code: 'redundant-event-modifier',
message: 'Touch event handlers that don\'t use the \'event\' object are passive by default' message: "Touch event handlers that don't use the 'event' object are passive by default"
}, },
redundant_event_modifier_passive: { redundant_event_modifier_passive: {
code: 'redundant-event-modifier', code: 'redundant-event-modifier',
@ -236,6 +267,7 @@ export default {
}), }),
avoid_mouse_events_on_document: { avoid_mouse_events_on_document: {
code: 'avoid-mouse-events-on-document', code: 'avoid-mouse-events-on-document',
message: 'Mouse enter/leave events on the document are not supported in all browsers and should be avoided' message:
'Mouse enter/leave events on the document are not supported in all browsers and should be avoided'
} }
}; };

@ -24,8 +24,8 @@ export default function create_module(
) { ) {
const internal_path = `${sveltePath}/internal`; const internal_path = `${sveltePath}/internal`;
helpers.sort((a, b) => (a.name < b.name) ? -1 : 1); helpers.sort((a, b) => (a.name < b.name ? -1 : 1));
globals.sort((a, b) => (a.name < b.name) ? -1 : 1); globals.sort((a, b) => (a.name < b.name ? -1 : 1));
const formatter = wrappers[format]; const formatter = wrappers[format];
@ -33,7 +33,18 @@ export default function create_module(
throw new Error(`options.format is invalid (must be ${list(Object.keys(wrappers))})`); throw new Error(`options.format is invalid (must be ${list(Object.keys(wrappers))})`);
} }
return formatter(program, name, banner, sveltePath, internal_path, helpers, globals, imports, module_exports, exports_from); return formatter(
program,
name,
banner,
sveltePath,
internal_path,
helpers,
globals,
imports,
module_exports,
exports_from
);
} }
function edit_source(source, sveltePath) { function edit_source(source, sveltePath) {
@ -46,14 +57,16 @@ function get_internal_globals(
globals: Array<{ name: string; alias: Identifier }>, globals: Array<{ name: string; alias: Identifier }>,
helpers: Array<{ name: string; alias: Identifier }> helpers: Array<{ name: string; alias: Identifier }>
) { ) {
return globals.length > 0 && { return (
globals.length > 0 && {
type: 'VariableDeclaration', type: 'VariableDeclaration',
kind: 'const', kind: 'const',
declarations: [{ declarations: [
{
type: 'VariableDeclarator', type: 'VariableDeclarator',
id: { id: {
type: 'ObjectPattern', type: 'ObjectPattern',
properties: globals.map(g => ({ properties: globals.map((g) => ({
type: 'Property', type: 'Property',
method: false, method: false,
shorthand: false, shorthand: false,
@ -64,8 +77,10 @@ function get_internal_globals(
})) }))
}, },
init: helpers.find(({ name }) => name === 'globals').alias init: helpers.find(({ name }) => name === 'globals').alias
}] }
}; ]
}
);
} }
function esm( function esm(
@ -82,7 +97,7 @@ function esm(
) { ) {
const import_declaration = { const import_declaration = {
type: 'ImportDeclaration', type: 'ImportDeclaration',
specifiers: helpers.map(h => ({ specifiers: helpers.map((h) => ({
type: 'ImportSpecifier', type: 'ImportSpecifier',
local: h.alias, local: h.alias,
imported: { type: 'Identifier', name: h.name } imported: { type: 'Identifier', name: h.name }
@ -105,7 +120,7 @@ function esm(
const exports = module_exports.length > 0 && { const exports = module_exports.length > 0 && {
type: 'ExportNamedDeclaration', type: 'ExportNamedDeclaration',
specifiers: module_exports.map(x => ({ specifiers: module_exports.map((x) => ({
type: 'Specifier', type: 'Specifier',
local: { type: 'Identifier', name: x.name }, local: { type: 'Identifier', name: x.name },
exported: { type: 'Identifier', name: x.as } exported: { type: 'Identifier', name: x.as }
@ -142,11 +157,12 @@ function cjs(
const internal_requires = { const internal_requires = {
type: 'VariableDeclaration', type: 'VariableDeclaration',
kind: 'const', kind: 'const',
declarations: [{ declarations: [
{
type: 'VariableDeclarator', type: 'VariableDeclarator',
id: { id: {
type: 'ObjectPattern', type: 'ObjectPattern',
properties: helpers.map(h => ({ properties: helpers.map((h) => ({
type: 'Property', type: 'Property',
method: false, method: false,
shorthand: false, shorthand: false,
@ -157,12 +173,13 @@ function cjs(
})) }))
}, },
init: x`require("${internal_path}")` init: x`require("${internal_path}")`
}] }
]
}; };
const internal_globals = get_internal_globals(globals, helpers); const internal_globals = get_internal_globals(globals, helpers);
const user_requires = imports.map(node => { const user_requires = imports.map((node) => {
const init = x`require("${edit_source(node.source.value, sveltePath)}")`; const init = x`require("${edit_source(node.source.value, sveltePath)}")`;
if (node.specifiers.length === 0) { if (node.specifiers.length === 0) {
return b`${init};`; return b`${init};`;
@ -170,32 +187,41 @@ function cjs(
return { return {
type: 'VariableDeclaration', type: 'VariableDeclaration',
kind: 'const', kind: 'const',
declarations: [{ declarations: [
{
type: 'VariableDeclarator', type: 'VariableDeclarator',
id: node.specifiers[0].type === 'ImportNamespaceSpecifier' id:
node.specifiers[0].type === 'ImportNamespaceSpecifier'
? { type: 'Identifier', name: node.specifiers[0].local.name } ? { type: 'Identifier', name: node.specifiers[0].local.name }
: { : {
type: 'ObjectPattern', type: 'ObjectPattern',
properties: node.specifiers.map(s => ({ properties: node.specifiers.map((s) => ({
type: 'Property', type: 'Property',
method: false, method: false,
shorthand: false, shorthand: false,
computed: false, computed: false,
key: s.type === 'ImportSpecifier' ? s.imported : { type: 'Identifier', name: 'default' }, key:
s.type === 'ImportSpecifier'
? s.imported
: { type: 'Identifier', name: 'default' },
value: s.local, value: s.local,
kind: 'init' kind: 'init'
})) }))
}, },
init init
}] }
]
}; };
}); });
const exports = module_exports.map(x => b`exports.${{ type: 'Identifier', name: x.as }} = ${{ type: 'Identifier', name: x.name }};`); const exports = module_exports.map(
(x) =>
b`exports.${{ type: 'Identifier', name: x.as }} = ${{ type: 'Identifier', name: x.name }};`
);
const user_exports_from = exports_from.map(node => { const user_exports_from = exports_from.map((node) => {
const init = x`require("${edit_source(node.source.value, sveltePath)}")`; const init = x`require("${edit_source(node.source.value, sveltePath)}")`;
return node.specifiers.map(specifier => { return node.specifiers.map((specifier) => {
return b`exports.${specifier.exported} = ${init}.${specifier.local};`; return b`exports.${specifier.exported} = ${init}.${specifier.local};`;
}); });
}); });

@ -18,7 +18,7 @@ enum BlockAppliesToNode {
} }
enum NodeExist { enum NodeExist {
Probably = 1, Probably = 1,
Definitely = 2, Definitely = 2
} }
const whitelist_attribute_selector = new Map([ const whitelist_attribute_selector = new Map([
@ -57,7 +57,7 @@ export default class Selector {
} }
apply(node: Element) { apply(node: Element) {
const to_encapsulate: Array<{ node: Element, block: Block }> = []; const to_encapsulate: Array<{ node: Element; block: Block }> = [];
apply_selector(this.local_blocks.slice(), node, to_encapsulate); apply_selector(this.local_blocks.slice(), node, to_encapsulate);
@ -85,7 +85,9 @@ export default class Selector {
} }
transform(code: MagicString, attr: string, max_amount_class_specificity_increased: number) { transform(code: MagicString, attr: string, max_amount_class_specificity_increased: number) {
const amount_class_specificity_to_increase = max_amount_class_specificity_increased - this.blocks.filter(block => block.should_encapsulate).length; const amount_class_specificity_to_increase =
max_amount_class_specificity_increased -
this.blocks.filter((block) => block.should_encapsulate).length;
function remove_global_pseudo_class(selector: CssNode) { function remove_global_pseudo_class(selector: CssNode) {
const first = selector.children[0]; const first = selector.children[0];
@ -125,7 +127,13 @@ export default class Selector {
if (block.global) { if (block.global) {
remove_global_pseudo_class(block.selectors[0]); remove_global_pseudo_class(block.selectors[0]);
} }
if (block.should_encapsulate) encapsulate_block(block, index === this.blocks.length - 1 ? attr.repeat(amount_class_specificity_to_increase + 1) : attr); if (block.should_encapsulate)
encapsulate_block(
block,
index === this.blocks.length - 1
? attr.repeat(amount_class_specificity_to_increase + 1)
: attr
);
}); });
} }
@ -172,10 +180,20 @@ export default class Selector {
for (let i = 0; i < this.blocks.length; i++) { for (let i = 0; i < this.blocks.length; i++) {
const block = this.blocks[i]; const block = this.blocks[i];
if (block.combinator && block.selectors.length === 0) { if (block.combinator && block.selectors.length === 0) {
component.error(this.node, compiler_errors.css_invalid_selector(component.source.slice(this.node.start, this.node.end))); component.error(
this.node,
compiler_errors.css_invalid_selector(
component.source.slice(this.node.start, this.node.end)
)
);
} }
if (!block.combinator && block.selectors.length === 0) { if (!block.combinator && block.selectors.length === 0) {
component.error(this.node, compiler_errors.css_invalid_selector(component.source.slice(this.node.start, this.node.end))); component.error(
this.node,
compiler_errors.css_invalid_selector(
component.source.slice(this.node.start, this.node.end)
)
);
} }
} }
} }
@ -184,7 +202,8 @@ export default class Selector {
for (const block of this.blocks) { for (const block of this.blocks) {
for (let index = 0; index < block.selectors.length; index++) { for (let index = 0; index < block.selectors.length; index++) {
const selector = block.selectors[index]; const selector = block.selectors[index];
if (selector.type === 'PseudoClassSelector' && if (
selector.type === 'PseudoClassSelector' &&
selector.name === 'global' && selector.name === 'global' &&
index !== 0 && index !== 0 &&
selector.children && selector.children &&
@ -208,14 +227,17 @@ export default class Selector {
} }
} }
function apply_selector(blocks: Block[], node: Element, to_encapsulate: Array<{ node: Element, block: Block }>): boolean { function apply_selector(
blocks: Block[],
node: Element,
to_encapsulate: Array<{ node: Element; block: Block }>
): boolean {
const block = blocks.pop(); const block = blocks.pop();
if (!block) return false; if (!block) return false;
if (!node) { if (!node) {
return ( return (
(block.global && blocks.every(block => block.global)) || (block.global && blocks.every((block) => block.global)) || (block.host && blocks.length === 0)
(block.host && blocks.length === 0)
); );
} }
@ -242,8 +264,10 @@ function apply_selector(blocks: Block[], node: Element, to_encapsulate: Array<{
} }
let parent = node; let parent = node;
while (parent = get_element_parent(parent)) { while ((parent = get_element_parent(parent))) {
if (block_might_apply_to_node(ancestor_block, parent) !== BlockAppliesToNode.NotPossible) { if (
block_might_apply_to_node(ancestor_block, parent) !== BlockAppliesToNode.NotPossible
) {
to_encapsulate.push({ node: parent, block: ancestor_block }); to_encapsulate.push({ node: parent, block: ancestor_block });
} }
} }
@ -254,14 +278,14 @@ function apply_selector(blocks: Block[], node: Element, to_encapsulate: Array<{
} }
} }
if (blocks.every(block => block.global)) { if (blocks.every((block) => block.global)) {
to_encapsulate.push({ node, block }); to_encapsulate.push({ node, block });
return true; return true;
} }
return false; return false;
} else if (block.combinator.name === '>') { } else if (block.combinator.name === '>') {
const has_global_parent = blocks.every(block => block.global); const has_global_parent = blocks.every((block) => block.global);
if (has_global_parent || apply_selector(blocks, get_element_parent(node), to_encapsulate)) { if (has_global_parent || apply_selector(blocks, get_element_parent(node), to_encapsulate)) {
to_encapsulate.push({ node, block }); to_encapsulate.push({ node, block });
return true; return true;
@ -275,7 +299,7 @@ function apply_selector(blocks: Block[], node: Element, to_encapsulate: Array<{
// NOTE: if we have :global(), we couldn't figure out what is selected within `:global` due to the // NOTE: if we have :global(), we couldn't figure out what is selected within `:global` due to the
// css-tree limitation that does not parse the inner selector of :global // css-tree limitation that does not parse the inner selector of :global
// so unless we are sure there will be no sibling to match, we will consider it as matched // so unless we are sure there will be no sibling to match, we will consider it as matched
const has_global = blocks.some(block => block.global); const has_global = blocks.some((block) => block.global);
if (has_global) { if (has_global) {
if (siblings.size === 0 && get_element_parent(node) !== null) { if (siblings.size === 0 && get_element_parent(node) !== null) {
return false; return false;
@ -309,13 +333,19 @@ function block_might_apply_to_node(block: Block, node: Element): BlockAppliesToN
while (i--) { while (i--) {
const selector = block.selectors[i]; const selector = block.selectors[i];
const name = typeof selector.name === 'string' && selector.name.replace(regex_backslash_and_following_character, '$1'); const name =
typeof selector.name === 'string' &&
selector.name.replace(regex_backslash_and_following_character, '$1');
if (selector.type === 'PseudoClassSelector' && (name === 'host' || name === 'root')) { if (selector.type === 'PseudoClassSelector' && (name === 'host' || name === 'root')) {
return BlockAppliesToNode.NotPossible; return BlockAppliesToNode.NotPossible;
} }
if (block.selectors.length === 1 && selector.type === 'PseudoClassSelector' && name === 'global') { if (
block.selectors.length === 1 &&
selector.type === 'PseudoClassSelector' &&
name === 'global'
) {
return BlockAppliesToNode.NotPossible; return BlockAppliesToNode.NotPossible;
} }
@ -324,17 +354,38 @@ function block_might_apply_to_node(block: Block, node: Element): BlockAppliesToN
} }
if (selector.type === 'ClassSelector') { if (selector.type === 'ClassSelector') {
if (!attribute_matches(node, 'class', name, '~=', false) && !node.classes.some(c => c.name === name)) return BlockAppliesToNode.NotPossible; if (
!attribute_matches(node, 'class', name, '~=', false) &&
!node.classes.some((c) => c.name === name)
)
return BlockAppliesToNode.NotPossible;
} else if (selector.type === 'IdSelector') { } else if (selector.type === 'IdSelector') {
if (!attribute_matches(node, 'id', name, '=', false)) return BlockAppliesToNode.NotPossible; if (!attribute_matches(node, 'id', name, '=', false)) return BlockAppliesToNode.NotPossible;
} else if (selector.type === 'AttributeSelector') { } else if (selector.type === 'AttributeSelector') {
if ( if (
!(whitelist_attribute_selector.has(node.name.toLowerCase()) && whitelist_attribute_selector.get(node.name.toLowerCase()).has(selector.name.name.toLowerCase())) && !(
!attribute_matches(node, selector.name.name, selector.value && unquote(selector.value), selector.matcher, selector.flags)) { whitelist_attribute_selector.has(node.name.toLowerCase()) &&
whitelist_attribute_selector
.get(node.name.toLowerCase())
.has(selector.name.name.toLowerCase())
) &&
!attribute_matches(
node,
selector.name.name,
selector.value && unquote(selector.value),
selector.matcher,
selector.flags
)
) {
return BlockAppliesToNode.NotPossible; return BlockAppliesToNode.NotPossible;
} }
} else if (selector.type === 'TypeSelector') { } else if (selector.type === 'TypeSelector') {
if (node.name.toLowerCase() !== name.toLowerCase() && name !== '*' && !node.is_dynamic_element) return BlockAppliesToNode.NotPossible; if (
node.name.toLowerCase() !== name.toLowerCase() &&
name !== '*' &&
!node.is_dynamic_element
)
return BlockAppliesToNode.NotPossible;
} else { } else {
return BlockAppliesToNode.UnknownSelectorType; return BlockAppliesToNode.UnknownSelectorType;
} }
@ -349,18 +400,31 @@ function test_attribute(operator, expected_value, case_insensitive, value) {
value = value.toLowerCase(); value = value.toLowerCase();
} }
switch (operator) { switch (operator) {
case '=': return value === expected_value; case '=':
case '~=': return value.split(/\s/).includes(expected_value); return value === expected_value;
case '|=': return `${value}-`.startsWith(`${expected_value}-`); case '~=':
case '^=': return value.startsWith(expected_value); return value.split(/\s/).includes(expected_value);
case '$=': return value.endsWith(expected_value); case '|=':
case '*=': return value.includes(expected_value); return `${value}-`.startsWith(`${expected_value}-`);
default: throw new Error("this shouldn't happen"); case '^=':
} return value.startsWith(expected_value);
} case '$=':
return value.endsWith(expected_value);
function attribute_matches(node: CssNode, name: string, expected_value: string, operator: string, case_insensitive: boolean) { case '*=':
const spread = node.attributes.find(attr => attr.type === 'Spread'); return value.includes(expected_value);
default:
throw new Error("this shouldn't happen");
}
}
function attribute_matches(
node: CssNode,
name: string,
expected_value: string,
operator: string,
case_insensitive: boolean
) {
const spread = node.attributes.find((attr) => attr.type === 'Spread');
if (spread) return true; if (spread) return true;
if (node.bindings.some((binding: CssNode) => binding.name === name)) return true; if (node.bindings.some((binding: CssNode) => binding.name === name)) return true;
@ -373,7 +437,8 @@ function attribute_matches(node: CssNode, name: string, expected_value: string,
if (attr.chunks.length === 1) { if (attr.chunks.length === 1) {
const value = attr.chunks[0]; const value = attr.chunks[0];
if (!value) return false; if (!value) return false;
if (value.type === 'Text') return test_attribute(operator, expected_value, case_insensitive, value.data); if (value.type === 'Text')
return test_attribute(operator, expected_value, case_insensitive, value.data);
} }
const possible_values = new Set(); const possible_values = new Set();
@ -403,7 +468,7 @@ function attribute_matches(node: CssNode, name: string, expected_value: string,
if (remaining.length > 0) { if (remaining.length > 0) {
if (start_with_space.length > 0) { if (start_with_space.length > 0) {
prev_values.forEach(prev_value => possible_values.add(prev_value)); prev_values.forEach((prev_value) => possible_values.add(prev_value));
} }
const combined = []; const combined = [];
@ -423,7 +488,7 @@ function attribute_matches(node: CssNode, name: string, expected_value: string,
}); });
continue; continue;
} else { } else {
prev_values.forEach(prev_value => possible_values.add(prev_value)); prev_values.forEach((prev_value) => possible_values.add(prev_value));
prev_values = []; prev_values = [];
} }
} }
@ -444,7 +509,7 @@ function attribute_matches(node: CssNode, name: string, expected_value: string,
return true; return true;
} }
} }
prev_values.forEach(prev_value => possible_values.add(prev_value)); prev_values.forEach((prev_value) => possible_values.add(prev_value));
if (possible_values.has(UNKNOWN)) return true; if (possible_values.has(UNKNOWN)) return true;
@ -458,7 +523,7 @@ function attribute_matches(node: CssNode, name: string, expected_value: string,
function unquote(value: CssNode) { function unquote(value: CssNode) {
if (value.type === 'Identifier') return value.name; if (value.type === 'Identifier') return value.name;
const str = value.value; const str = value.value;
if (str[0] === str[str.length - 1] && str[0] === "'" || str[0] === '"') { if ((str[0] === str[str.length - 1] && str[0] === "'") || str[0] === '"') {
return str.slice(1, str.length - 1); return str.slice(1, str.length - 1);
} }
return str; return str;
@ -506,12 +571,19 @@ function find_previous_sibling(node: INode): INode {
return current_node; return current_node;
} }
function get_possible_element_siblings(node: INode, adjacent_only: boolean): Map<Element, NodeExist> { function get_possible_element_siblings(
node: INode,
adjacent_only: boolean
): Map<Element, NodeExist> {
const result: Map<Element, NodeExist> = new Map(); const result: Map<Element, NodeExist> = new Map();
let prev: INode = node; let prev: INode = node;
while (prev = find_previous_sibling(prev)) { while ((prev = find_previous_sibling(prev))) {
if (prev.type === 'Element') { if (prev.type === 'Element') {
if (!prev.attributes.find(attr => attr.type === 'Attribute' && attr.name.toLowerCase() === 'slot')) { if (
!prev.attributes.find(
(attr) => attr.type === 'Attribute' && attr.name.toLowerCase() === 'slot'
)
) {
result.set(prev, NodeExist.Definitely); result.set(prev, NodeExist.Definitely);
} }
@ -531,7 +603,13 @@ function get_possible_element_siblings(node: INode, adjacent_only: boolean): Map
if (!prev || !adjacent_only) { if (!prev || !adjacent_only) {
let parent: INode = node; let parent: INode = node;
let skip_each_for_last_child = node.type === 'ElseBlock'; let skip_each_for_last_child = node.type === 'ElseBlock';
while ((parent = parent.parent) && (parent.type === 'EachBlock' || parent.type === 'IfBlock' || parent.type === 'ElseBlock' || parent.type === 'AwaitBlock')) { while (
(parent = parent.parent) &&
(parent.type === 'EachBlock' ||
parent.type === 'IfBlock' ||
parent.type === 'ElseBlock' ||
parent.type === 'AwaitBlock')
) {
const possible_siblings = get_possible_element_siblings(parent, adjacent_only); const possible_siblings = get_possible_element_siblings(parent, adjacent_only);
add_to_map(possible_siblings, result); add_to_map(possible_siblings, result);
@ -556,12 +634,17 @@ function get_possible_element_siblings(node: INode, adjacent_only: boolean): Map
return result; return result;
} }
function get_possible_last_child(block: EachBlock | IfBlock | AwaitBlock, adjacent_only: boolean): Map<Element, NodeExist> { function get_possible_last_child(
block: EachBlock | IfBlock | AwaitBlock,
adjacent_only: boolean
): Map<Element, NodeExist> {
const result: Map<Element, NodeExist> = new Map(); const result: Map<Element, NodeExist> = new Map();
if (block.type === 'EachBlock') { if (block.type === 'EachBlock') {
const each_result: Map<Element, NodeExist> = loop_child(block.children, adjacent_only); const each_result: Map<Element, NodeExist> = loop_child(block.children, adjacent_only);
const else_result: Map<Element, NodeExist> = block.else ? loop_child(block.else.children, adjacent_only) : new Map(); const else_result: Map<Element, NodeExist> = block.else
? loop_child(block.else.children, adjacent_only)
: new Map();
const not_exhaustive = !has_definite_elements(else_result); const not_exhaustive = !has_definite_elements(else_result);
@ -573,7 +656,9 @@ function get_possible_last_child(block: EachBlock | IfBlock | AwaitBlock, adjace
add_to_map(else_result, result); add_to_map(else_result, result);
} else if (block.type === 'IfBlock') { } else if (block.type === 'IfBlock') {
const if_result: Map<Element, NodeExist> = loop_child(block.children, adjacent_only); const if_result: Map<Element, NodeExist> = loop_child(block.children, adjacent_only);
const else_result: Map<Element, NodeExist> = block.else ? loop_child(block.else.children, adjacent_only) : new Map(); const else_result: Map<Element, NodeExist> = block.else
? loop_child(block.else.children, adjacent_only)
: new Map();
const not_exhaustive = !has_definite_elements(if_result) || !has_definite_elements(else_result); const not_exhaustive = !has_definite_elements(if_result) || !has_definite_elements(else_result);
@ -585,11 +670,20 @@ function get_possible_last_child(block: EachBlock | IfBlock | AwaitBlock, adjace
add_to_map(if_result, result); add_to_map(if_result, result);
add_to_map(else_result, result); add_to_map(else_result, result);
} else if (block.type === 'AwaitBlock') { } else if (block.type === 'AwaitBlock') {
const pending_result: Map<Element, NodeExist> = block.pending ? loop_child(block.pending.children, adjacent_only) : new Map(); const pending_result: Map<Element, NodeExist> = block.pending
const then_result: Map<Element, NodeExist> = block.then ? loop_child(block.then.children, adjacent_only) : new Map(); ? loop_child(block.pending.children, adjacent_only)
const catch_result: Map<Element, NodeExist> = block.catch ? loop_child(block.catch.children, adjacent_only) : new Map(); : new Map();
const then_result: Map<Element, NodeExist> = block.then
const not_exhaustive = !has_definite_elements(pending_result) || !has_definite_elements(then_result) || !has_definite_elements(catch_result); ? loop_child(block.then.children, adjacent_only)
: new Map();
const catch_result: Map<Element, NodeExist> = block.catch
? loop_child(block.catch.children, adjacent_only)
: new Map();
const not_exhaustive =
!has_definite_elements(pending_result) ||
!has_definite_elements(then_result) ||
!has_definite_elements(catch_result);
if (not_exhaustive) { if (not_exhaustive) {
mark_as_probably(pending_result); mark_as_probably(pending_result);
@ -641,7 +735,11 @@ function loop_child(children: INode[], adjacent_only: boolean) {
if (adjacent_only) { if (adjacent_only) {
break; break;
} }
} else if (child.type === 'EachBlock' || child.type === 'IfBlock' || child.type === 'AwaitBlock') { } else if (
child.type === 'EachBlock' ||
child.type === 'IfBlock' ||
child.type === 'AwaitBlock'
) {
const child_result = get_possible_last_child(child, adjacent_only); const child_result = get_possible_last_child(child, adjacent_only);
add_to_map(child_result, result); add_to_map(child_result, result);
if (adjacent_only && has_definite_elements(child_result)) { if (adjacent_only && has_definite_elements(child_result)) {
@ -678,7 +776,7 @@ class Block {
this.start = selector.start; this.start = selector.start;
this.host = selector.type === 'PseudoClassSelector' && selector.name === 'host'; this.host = selector.type === 'PseudoClassSelector' && selector.name === 'host';
} }
this.root = this.root || selector.type === 'PseudoClassSelector' && selector.name === 'root'; this.root = this.root || (selector.type === 'PseudoClassSelector' && selector.name === 'root');
this.selectors.push(selector); this.selectors.push(selector);
this.end = selector.end; this.end = selector.end;
@ -689,7 +787,10 @@ class Block {
this.selectors.length >= 1 && this.selectors.length >= 1 &&
this.selectors[0].type === 'PseudoClassSelector' && this.selectors[0].type === 'PseudoClassSelector' &&
this.selectors[0].name === 'global' && this.selectors[0].name === 'global' &&
this.selectors.every((selector) => selector.type === 'PseudoClassSelector' || selector.type === 'PseudoElementSelector') this.selectors.every(
(selector) =>
selector.type === 'PseudoClassSelector' || selector.type === 'PseudoElementSelector'
)
); );
} }
} }

@ -17,13 +17,10 @@ function remove_css_prefix(name: string): string {
return name.replace(regex_css_browser_prefix, ''); return name.replace(regex_css_browser_prefix, '');
} }
const is_keyframes_node = (node: CssNode) => const is_keyframes_node = (node: CssNode) => remove_css_prefix(node.name) === 'keyframes';
remove_css_prefix(node.name) === 'keyframes';
const at_rule_has_declaration = ({ block }: CssNode): true => const at_rule_has_declaration = ({ block }: CssNode): true =>
block && block && block.children && block.children.find((node: CssNode) => node.type === 'Declaration');
block.children &&
block.children.find((node: CssNode) => node.type === 'Declaration');
function minify_declarations( function minify_declarations(
code: MagicString, code: MagicString,
@ -34,7 +31,7 @@ function minify_declarations(
declarations.forEach((declaration, i) => { declarations.forEach((declaration, i) => {
const separator = i > 0 ? ';' : ''; const separator = i > 0 ? ';' : '';
if ((declaration.node.start - c) > separator.length) { if (declaration.node.start - c > separator.length) {
code.update(c, declaration.node.start, separator); code.update(c, declaration.node.start, separator);
} }
declaration.minify(code); declaration.minify(code);
@ -58,13 +55,14 @@ class Rule {
} }
apply(node: Element) { apply(node: Element) {
this.selectors.forEach(selector => selector.apply(node)); // TODO move the logic in here? this.selectors.forEach((selector) => selector.apply(node)); // TODO move the logic in here?
} }
is_used(dev: boolean) { is_used(dev: boolean) {
if (this.parent && this.parent.node.type === 'Atrule' && is_keyframes_node(this.parent.node)) return true; if (this.parent && this.parent.node.type === 'Atrule' && is_keyframes_node(this.parent.node))
return true;
if (this.declarations.length === 0) return dev; if (this.declarations.length === 0) return dev;
return this.selectors.some(s => s.used); return this.selectors.some((s) => s.used);
} }
minify(code: MagicString, _dev: boolean) { minify(code: MagicString, _dev: boolean) {
@ -74,7 +72,7 @@ class Rule {
this.selectors.forEach((selector) => { this.selectors.forEach((selector) => {
if (selector.used) { if (selector.used) {
const separator = started ? ',' : ''; const separator = started ? ',' : '';
if ((selector.node.start - c) > separator.length) { if (selector.node.start - c > separator.length) {
code.update(c, selector.node.start, separator); code.update(c, selector.node.start, separator);
} }
@ -93,29 +91,39 @@ class Rule {
code.remove(c, this.node.block.end - 1); code.remove(c, this.node.block.end - 1);
} }
transform(code: MagicString, id: string, keyframes: Map<string, string>, max_amount_class_specificity_increased: number) { transform(
if (this.parent && this.parent.node.type === 'Atrule' && is_keyframes_node(this.parent.node)) return true; code: MagicString,
id: string,
keyframes: Map<string, string>,
max_amount_class_specificity_increased: number
) {
if (this.parent && this.parent.node.type === 'Atrule' && is_keyframes_node(this.parent.node))
return true;
const attr = `.${id}`; const attr = `.${id}`;
this.selectors.forEach(selector => selector.transform(code, attr, max_amount_class_specificity_increased)); this.selectors.forEach((selector) =>
this.declarations.forEach(declaration => declaration.transform(code, keyframes)); selector.transform(code, attr, max_amount_class_specificity_increased)
);
this.declarations.forEach((declaration) => declaration.transform(code, keyframes));
} }
validate(component: Component) { validate(component: Component) {
this.selectors.forEach(selector => { this.selectors.forEach((selector) => {
selector.validate(component); selector.validate(component);
}); });
} }
warn_on_unused_selector(handler: (selector: Selector) => void) { warn_on_unused_selector(handler: (selector: Selector) => void) {
this.selectors.forEach(selector => { this.selectors.forEach((selector) => {
if (!selector.used) handler(selector); if (!selector.used) handler(selector);
}); });
} }
get_max_amount_class_specificity_increased() { get_max_amount_class_specificity_increased() {
return Math.max(...this.selectors.map(selector => selector.get_amount_class_specificity_increased())); return Math.max(
...this.selectors.map((selector) => selector.get_amount_class_specificity_increased())
);
} }
} }
@ -144,9 +152,7 @@ class Declaration {
if (!this.node.property) return; // @apply, and possibly other weird cases? if (!this.node.property) return; // @apply, and possibly other weird cases?
const c = this.node.start + this.node.property.length; const c = this.node.start + this.node.property.length;
const first = this.node.value.children const first = this.node.value.children ? this.node.value.children[0] : this.node.value;
? this.node.value.children[0]
: this.node.value;
// Don't minify whitespace in custom properties, since some browsers (Chromium < 99) // Don't minify whitespace in custom properties, since some browsers (Chromium < 99)
// treat --foo: ; and --foo:; differently // treat --foo: ; and --foo:; differently
@ -173,13 +179,18 @@ class Atrule {
} }
apply(node: Element) { apply(node: Element) {
if (this.node.name === 'container' || this.node.name === 'media' || this.node.name === 'supports' || this.node.name === 'layer') { if (
this.children.forEach(child => { this.node.name === 'container' ||
this.node.name === 'media' ||
this.node.name === 'supports' ||
this.node.name === 'layer'
) {
this.children.forEach((child) => {
child.apply(node); child.apply(node);
}); });
} else if (is_keyframes_node(this.node)) { } else if (is_keyframes_node(this.node)) {
this.children.forEach((rule: Rule) => { this.children.forEach((rule: Rule) => {
rule.selectors.forEach(selector => { rule.selectors.forEach((selector) => {
selector.used = true; selector.used = true;
}); });
}); });
@ -231,7 +242,7 @@ class Atrule {
if (this.children.length) c++; if (this.children.length) c++;
} }
this.children.forEach(child => { this.children.forEach((child) => {
if (child.is_used(dev)) { if (child.is_used(dev)) {
code.remove(c, child.node.start); code.remove(c, child.node.start);
child.minify(code, dev); child.minify(code, dev);
@ -243,14 +254,19 @@ class Atrule {
} }
} }
transform(code: MagicString, id: string, keyframes: Map<string, string>, max_amount_class_specificity_increased: number) { transform(
code: MagicString,
id: string,
keyframes: Map<string, string>,
max_amount_class_specificity_increased: number
) {
if (is_keyframes_node(this.node)) { if (is_keyframes_node(this.node)) {
this.node.prelude.children.forEach(({ type, name, start, end }: CssNode) => { this.node.prelude.children.forEach(({ type, name, start, end }: CssNode) => {
if (type === 'Identifier') { if (type === 'Identifier') {
if (name.startsWith('-global-')) { if (name.startsWith('-global-')) {
code.remove(start, start + 8); code.remove(start, start + 8);
this.children.forEach((rule: Rule) => { this.children.forEach((rule: Rule) => {
rule.selectors.forEach(selector => { rule.selectors.forEach((selector) => {
selector.used = true; selector.used = true;
}); });
}); });
@ -261,13 +277,13 @@ class Atrule {
}); });
} }
this.children.forEach(child => { this.children.forEach((child) => {
child.transform(code, id, keyframes, max_amount_class_specificity_increased); child.transform(code, id, keyframes, max_amount_class_specificity_increased);
}); });
} }
validate(component: Component) { validate(component: Component) {
this.children.forEach(child => { this.children.forEach((child) => {
child.validate(component); child.validate(component);
}); });
} }
@ -275,13 +291,15 @@ class Atrule {
warn_on_unused_selector(handler: (selector: Selector) => void) { warn_on_unused_selector(handler: (selector: Selector) => void) {
if (this.node.name !== 'media') return; if (this.node.name !== 'media') return;
this.children.forEach(child => { this.children.forEach((child) => {
child.warn_on_unused_selector(handler); child.warn_on_unused_selector(handler);
}); });
} }
get_max_amount_class_specificity_increased() { get_max_amount_class_specificity_increased() {
return Math.max(...this.children.map(rule => rule.get_max_amount_class_specificity_increased())); return Math.max(
...this.children.map((rule) => rule.get_max_amount_class_specificity_increased())
);
} }
} }
@ -357,8 +375,8 @@ export default class Stylesheet {
}); });
} else if (at_rule_has_declaration(node)) { } else if (at_rule_has_declaration(node)) {
const at_rule_declarations = node.block.children const at_rule_declarations = node.block.children
.filter(node => node.type === 'Declaration') .filter((node) => node.type === 'Declaration')
.map(node => new Declaration(node)); .map((node) => new Declaration(node));
push_array(atrule.declarations, at_rule_declarations); push_array(atrule.declarations, at_rule_declarations);
} }
@ -421,13 +439,15 @@ export default class Stylesheet {
} }
}); });
const max = Math.max(...this.children.map(rule => rule.get_max_amount_class_specificity_increased())); const max = Math.max(
this.children.forEach((child: (Atrule | Rule)) => { ...this.children.map((rule) => rule.get_max_amount_class_specificity_increased())
);
this.children.forEach((child: Atrule | Rule) => {
child.transform(code, this.id, this.keyframes, max); child.transform(code, this.id, this.keyframes, max);
}); });
let c = 0; let c = 0;
this.children.forEach(child => { this.children.forEach((child) => {
if (child.is_used(this.dev)) { if (child.is_used(this.dev)) {
code.remove(c, child.node.start); code.remove(c, child.node.start);
child.minify(code, this.dev); child.minify(code, this.dev);
@ -448,17 +468,24 @@ export default class Stylesheet {
} }
validate(component: Component) { validate(component: Component) {
this.children.forEach(child => { this.children.forEach((child) => {
child.validate(component); child.validate(component);
}); });
} }
warn_on_unused_selectors(component: Component) { warn_on_unused_selectors(component: Component) {
const ignores = !this.ast.css ? [] : extract_ignores_above_position(this.ast.css.start, this.ast.html.children); const ignores = !this.ast.css
? []
: extract_ignores_above_position(this.ast.css.start, this.ast.html.children);
component.push_ignores(ignores); component.push_ignores(ignores);
this.children.forEach(child => { this.children.forEach((child) => {
child.warn_on_unused_selector((selector: Selector) => { child.warn_on_unused_selector((selector: Selector) => {
component.warn(selector.node, compiler_warnings.css_unused_selector(this.source.slice(selector.node.start, selector.node.end))); component.warn(
selector.node,
compiler_warnings.css_unused_selector(
this.source.slice(selector.node.start, selector.node.end)
)
);
}); });
}); });
component.pop_ignores(); component.pop_ignores();

@ -35,13 +35,7 @@ const valid_options = [
'cssHash' 'cssHash'
]; ];
const valid_css_values = [ const valid_css_values = [true, false, 'injected', 'external', 'none'];
true,
false,
'injected',
'external',
'none'
];
const regex_valid_identifier = /^[a-zA-Z_$][a-zA-Z_$0-9]*$/; const regex_valid_identifier = /^[a-zA-Z_$][a-zA-Z_$0-9]*$/;
const regex_starts_with_lowercase_character = /^[a-z]/; const regex_starts_with_lowercase_character = /^[a-z]/;
@ -49,7 +43,7 @@ const regex_starts_with_lowercase_character = /^[a-z]/;
function validate_options(options: CompileOptions, warnings: Warning[]) { function validate_options(options: CompileOptions, warnings: Warning[]) {
const { name, filename, loopGuardTimeout, dev, namespace, css } = options; const { name, filename, loopGuardTimeout, dev, namespace, css } = options;
Object.keys(options).forEach(key => { Object.keys(options).forEach((key) => {
if (!valid_options.includes(key)) { if (!valid_options.includes(key)) {
const match = fuzzymatch(key, valid_options); const match = fuzzymatch(key, valid_options);
let message = `Unrecognized option '${key}'`; let message = `Unrecognized option '${key}'`;
@ -84,7 +78,9 @@ function validate_options(options: CompileOptions, warnings: Warning[]) {
} }
if (valid_css_values.indexOf(css) === -1) { if (valid_css_values.indexOf(css) === -1) {
throw new Error(`options.css must be true, false, 'injected', 'external', or 'none' (got '${css}')`); throw new Error(
`options.css must be true, false, 'injected', 'external', or 'none' (got '${css}')`
);
} }
if (css === true || css === false) { if (css === true || css === false) {
@ -111,7 +107,10 @@ function validate_options(options: CompileOptions, warnings: Warning[]) {
} }
export default function compile(source: string, options: CompileOptions = {}) { export default function compile(source: string, options: CompileOptions = {}) {
options = Object.assign({ generate: 'dom', dev: false, enableSourcemap: true, css: 'injected' }, options); options = Object.assign(
{ generate: 'dom', dev: false, enableSourcemap: true, css: 'injected' },
options
);
const stats = new Stats(); const stats = new Stats();
const warnings = []; const warnings = [];
@ -133,7 +132,8 @@ export default function compile(source: string, options: CompileOptions = {}) {
); );
stats.stop('create component'); stats.stop('create component');
const result = options.generate === false const result =
options.generate === false
? null ? null
: options.generate === 'ssr' : options.generate === 'ssr'
? render_ssr(component, options) ? render_ssr(component, options)

@ -48,7 +48,7 @@ export default class Attribute extends Node {
this.chunks = this.is_true this.chunks = this.is_true
? [] ? []
: info.value.map(node => { : info.value.map((node) => {
if (node.type === 'Text') return node; if (node.type === 'Text') return node;
this.is_static = false; this.is_static = false;
@ -70,7 +70,7 @@ export default class Attribute extends Node {
if (this.is_spread) return this.expression.dynamic_dependencies(); if (this.is_spread) return this.expression.dynamic_dependencies();
const dependencies: Set<string> = new Set(); const dependencies: Set<string> = new Set();
this.chunks.forEach(chunk => { this.chunks.forEach((chunk) => {
if (chunk.type === 'Expression') { if (chunk.type === 'Expression') {
add_to_set(dependencies, chunk.dynamic_dependencies()); add_to_set(dependencies, chunk.dynamic_dependencies());
} }
@ -90,7 +90,9 @@ export default class Attribute extends Node {
} }
let expression = this.chunks let expression = this.chunks
.map(chunk => chunk.type === 'Text' ? string_literal(chunk.data) : chunk.manipulate(block)) .map((chunk) =>
chunk.type === 'Text' ? string_literal(chunk.data) : chunk.manipulate(block)
)
.reduce((lhs, rhs) => x`${lhs} + ${rhs}`); .reduce((lhs, rhs) => x`${lhs} + ${rhs}`);
if (this.chunks[0].type !== 'Text') { if (this.chunks[0].type !== 'Text') {
@ -106,8 +108,8 @@ export default class Attribute extends Node {
return this.is_true return this.is_true
? true ? true
: this.chunks[0] : this.chunks[0]
// method should be called only when `is_static = true` ? // method should be called only when `is_static = true`
? (this.chunks[0] as Text).data (this.chunks[0] as Text).data
: ''; : '';
} }
@ -115,8 +117,8 @@ export default class Attribute extends Node {
return this.is_static return this.is_static
? false ? false
: this.chunks.length === 1 : this.chunks.length === 1
// @ts-ignore todo: probably error ? // @ts-ignore todo: probably error
? this.chunks[0].node.type !== 'Identifier' || this.scope.names.has(this.chunks[0].node.name) this.chunks[0].node.type !== 'Identifier' || this.scope.names.has(this.chunks[0].node.name)
: true; : true;
} }
} }

@ -37,12 +37,24 @@ export default class AwaitBlock extends Node {
if (this.then_node) { if (this.then_node) {
this.then_contexts = []; this.then_contexts = [];
unpack_destructuring({ contexts: this.then_contexts, node: info.value, scope, component, context_rest_properties: this.context_rest_properties }); unpack_destructuring({
contexts: this.then_contexts,
node: info.value,
scope,
component,
context_rest_properties: this.context_rest_properties
});
} }
if (this.catch_node) { if (this.catch_node) {
this.catch_contexts = []; this.catch_contexts = [];
unpack_destructuring({ contexts: this.catch_contexts, node: info.error, scope, component, context_rest_properties: this.context_rest_properties }); unpack_destructuring({
contexts: this.catch_contexts,
node: info.error,
scope,
component,
context_rest_properties: this.context_rest_properties
});
} }
this.pending = new PendingBlock(component, this, scope, info.pending); this.pending = new PendingBlock(component, this, scope, info.pending);

@ -37,7 +37,12 @@ export default class Binding extends Node {
is_contextual: boolean; is_contextual: boolean;
is_readonly: boolean; is_readonly: boolean;
constructor(component: Component, parent: Element | InlineComponent | Window | Document, scope: TemplateScope, info: TemplateNode) { constructor(
component: Component,
parent: Element | InlineComponent | Window | Document,
scope: TemplateScope,
info: TemplateNode
) {
super(component, parent, scope, info); super(component, parent, scope, info);
if (info.expression.type !== 'Identifier' && info.expression.type !== 'MemberExpression') { if (info.expression.type !== 'Identifier' && info.expression.type !== 'MemberExpression') {
@ -51,7 +56,9 @@ export default class Binding extends Node {
const { name } = get_object(this.expression.node); const { name } = get_object(this.expression.node);
this.is_contextual = Array.from(this.expression.references).some(name => scope.names.has(name)); this.is_contextual = Array.from(this.expression.references).some((name) =>
scope.names.has(name)
);
if (this.is_contextual) this.validate_binding_rest_properties(scope); if (this.is_contextual) this.validate_binding_rest_properties(scope);
// make sure we track this as a mutable ref // make sure we track this as a mutable ref
@ -67,7 +74,7 @@ export default class Binding extends Node {
component.error(this, compiler_errors.invalid_binding_const); component.error(this, compiler_errors.invalid_binding_const);
} }
scope.dependencies_for_name.get(name).forEach(name => { scope.dependencies_for_name.get(name).forEach((name) => {
const variable = component.var_lookup.get(name); const variable = component.var_lookup.get(name);
if (variable) { if (variable) {
variable.mutated = true; variable.mutated = true;
@ -96,7 +103,7 @@ export default class Binding extends Node {
regex_box_size.test(this.name) || regex_box_size.test(this.name) ||
(isElement(parent) && (isElement(parent) &&
((parent.is_media_node() && read_only_media_attributes.has(this.name)) || ((parent.is_media_node() && read_only_media_attributes.has(this.name)) ||
(parent.name === 'input' && type === 'file')) /* TODO others? */); (parent.name === 'input' && type === 'file'))) /* TODO others? */;
} }
is_readonly_media_attribute() { is_readonly_media_attribute() {
@ -104,12 +111,15 @@ export default class Binding extends Node {
} }
validate_binding_rest_properties(scope: TemplateScope) { validate_binding_rest_properties(scope: TemplateScope) {
this.expression.references.forEach(name => { this.expression.references.forEach((name) => {
const each_block = scope.get_owner(name); const each_block = scope.get_owner(name);
if (each_block && each_block.type === 'EachBlock') { if (each_block && each_block.type === 'EachBlock') {
const rest_node = each_block.context_rest_properties.get(name); const rest_node = each_block.context_rest_properties.get(name);
if (rest_node) { if (rest_node) {
this.component.warn(rest_node as any, compiler_warnings.invalid_rest_eachblock_binding(name)); this.component.warn(
rest_node as any,
compiler_warnings.invalid_rest_eachblock_binding(name)
);
} }
} }
}); });

@ -16,13 +16,13 @@ export default class CatchBlock extends AbstractBlock {
this.scope = scope.child(); this.scope = scope.child();
if (parent.catch_node) { if (parent.catch_node) {
parent.catch_contexts.forEach(context => { parent.catch_contexts.forEach((context) => {
if (context.type !== 'DestructuredVariable') return; if (context.type !== 'DestructuredVariable') return;
this.scope.add(context.key.name, parent.expression.dependencies, this); this.scope.add(context.key.name, parent.expression.dependencies, this);
}); });
} }
([this.const_tags, this.children] = get_const_tags(info.children, component, this, parent)); [this.const_tags, this.children] = get_const_tags(info.children, component, this, parent);
if (!info.skip) { if (!info.skip) {
this.warn_if_empty_block(); this.warn_if_empty_block();

@ -12,7 +12,15 @@ import get_object from '../utils/get_object';
import compiler_errors from '../compiler_errors'; import compiler_errors from '../compiler_errors';
import { Node as ESTreeNode } from 'estree'; import { Node as ESTreeNode } from 'estree';
const allowed_parents = new Set(['EachBlock', 'CatchBlock', 'ThenBlock', 'InlineComponent', 'SlotTemplate', 'IfBlock', 'ElseBlock']); const allowed_parents = new Set([
'EachBlock',
'CatchBlock',
'ThenBlock',
'InlineComponent',
'SlotTemplate',
'IfBlock',
'ElseBlock'
]);
export default class ConstTag extends Node { export default class ConstTag extends Node {
type: 'ConstTag'; type: 'ConstTag';
@ -25,7 +33,12 @@ export default class ConstTag extends Node {
assignees: Set<string> = new Set(); assignees: Set<string> = new Set();
dependencies: Set<string> = new Set(); dependencies: Set<string> = new Set();
constructor(component: Component, parent: INodeAllowConstTag, scope: TemplateScope, info: ConstTagType) { constructor(
component: Component,
parent: INodeAllowConstTag,
scope: TemplateScope,
info: ConstTagType
) {
super(component, parent, scope, info); super(component, parent, scope, info);
if (!allowed_parents.has(parent.type)) { if (!allowed_parents.has(parent.type)) {
@ -46,7 +59,9 @@ export default class ConstTag extends Node {
walk(info.expression.right, { walk(info.expression.right, {
enter(node, parent) { enter(node, parent) {
if (is_reference(node as NodeWithPropertyDefinition, parent as NodeWithPropertyDefinition)) { if (
is_reference(node as NodeWithPropertyDefinition, parent as NodeWithPropertyDefinition)
) {
const identifier = get_object(node as any); const identifier = get_object(node as any);
const { name } = identifier; const { name } = identifier;
dependencies.add(name); dependencies.add(name);
@ -64,11 +79,14 @@ export default class ConstTag extends Node {
context_rest_properties: this.context_rest_properties context_rest_properties: this.context_rest_properties
}); });
this.expression = new Expression(this.component, this, this.scope, this.node.expression.right); this.expression = new Expression(this.component, this, this.scope, this.node.expression.right);
this.contexts.forEach(context => { this.contexts.forEach((context) => {
if (context.type !== 'DestructuredVariable') return; if (context.type !== 'DestructuredVariable') return;
const owner = this.scope.get_owner(context.key.name); const owner = this.scope.get_owner(context.key.name);
if (owner && owner.type === 'ConstTag' && owner.parent === this.parent) { if (owner && owner.type === 'ConstTag' && owner.parent === this.parent) {
this.component.error(this.node, compiler_errors.invalid_const_declaration(context.key.name)); this.component.error(
this.node,
compiler_errors.invalid_const_declaration(context.key.name)
);
} }
this.scope.add(context.key.name, this.expression.dependencies, this); this.scope.add(context.key.name, this.expression.dependencies, this);
}); });

@ -10,10 +10,7 @@ import { Element } from '../../interfaces';
import compiler_warnings from '../compiler_warnings'; import compiler_warnings from '../compiler_warnings';
import compiler_errors from '../compiler_errors'; import compiler_errors from '../compiler_errors';
const valid_bindings = [ const valid_bindings = ['fullscreenElement', 'visibilityState'];
'fullscreenElement',
'visibilityState'
];
export default class Document extends Node { export default class Document extends Node {
type: 'Document'; type: 'Document';
@ -31,9 +28,23 @@ export default class Document extends Node {
if (!~valid_bindings.indexOf(node.name)) { if (!~valid_bindings.indexOf(node.name)) {
const match = fuzzymatch(node.name, valid_bindings); const match = fuzzymatch(node.name, valid_bindings);
if (match) { if (match) {
return component.error(node, compiler_errors.invalid_binding_on(node.name, '<svelte:document>', ` (did you mean '${match}'?)`)); return component.error(
node,
compiler_errors.invalid_binding_on(
node.name,
'<svelte:document>',
` (did you mean '${match}'?)`
)
);
} else { } else {
return component.error(node, compiler_errors.invalid_binding_on(node.name, '<svelte:document>', ` — valid bindings are ${list(valid_bindings)}`)); return component.error(
node,
compiler_errors.invalid_binding_on(
node.name,
'<svelte:document>',
` — valid bindings are ${list(valid_bindings)}`
)
);
} }
} }
@ -51,9 +62,7 @@ export default class Document extends Node {
private validate() { private validate() {
const handlers_map = new Set(); const handlers_map = new Set();
this.handlers.forEach(handler => ( this.handlers.forEach((handler) => handlers_map.add(handler.name));
handlers_map.add(handler.name)
));
if (handlers_map.has('mouseenter') || handlers_map.has('mouseleave')) { if (handlers_map.has('mouseenter') || handlers_map.has('mouseleave')) {
this.component.warn(this, compiler_warnings.avoid_mouse_events_on_document); this.component.warn(this, compiler_warnings.avoid_mouse_events_on_document);

@ -44,9 +44,15 @@ export default class EachBlock extends AbstractBlock {
this.scope = scope.child(); this.scope = scope.child();
this.context_rest_properties = new Map(); this.context_rest_properties = new Map();
this.contexts = []; this.contexts = [];
unpack_destructuring({ contexts: this.contexts, node: info.context, scope, component, context_rest_properties: this.context_rest_properties }); unpack_destructuring({
contexts: this.contexts,
node: info.context,
scope,
component,
context_rest_properties: this.context_rest_properties
});
this.contexts.forEach(context => { this.contexts.forEach((context) => {
if (context.type !== 'DestructuredVariable') return; if (context.type !== 'DestructuredVariable') return;
this.scope.add(context.key.name, this.expression.dependencies, this); this.scope.add(context.key.name, this.expression.dependencies, this);
}); });
@ -57,19 +63,17 @@ export default class EachBlock extends AbstractBlock {
this.scope.add(this.index, dependencies, this); this.scope.add(this.index, dependencies, this);
} }
this.key = info.key this.key = info.key ? new Expression(component, this, this.scope, info.key) : null;
? new Expression(component, this, this.scope, info.key)
: null;
this.has_animation = false; this.has_animation = false;
([this.const_tags, this.children] = get_const_tags(info.children, component, this, this)); [this.const_tags, this.children] = get_const_tags(info.children, component, this, this);
if (this.has_animation) { if (this.has_animation) {
this.children = this.children.filter(child => !isEmptyNode(child) && !isCommentNode(child)); this.children = this.children.filter((child) => !isEmptyNode(child) && !isCommentNode(child));
if (this.children.length !== 1) { if (this.children.length !== 1) {
const child = this.children.find(child => !!(child as Element).animation); const child = this.children.find((child) => !!(child as Element).animation);
component.error((child as Element).animation, compiler_errors.invalid_animation_sole); component.error((child as Element).animation, compiler_errors.invalid_animation_sole);
return; return;
} }
@ -77,9 +81,7 @@ export default class EachBlock extends AbstractBlock {
this.warn_if_empty_block(); this.warn_if_empty_block();
this.else = info.else this.else = info.else ? new ElseBlock(component, this, this.scope, info.else) : null;
? new ElseBlock(component, this, this.scope, info.else)
: null;
} }
} }

@ -11,8 +11,17 @@ import StyleDirective from './StyleDirective';
import Text from './Text'; import Text from './Text';
import { namespaces } from '../../utils/namespaces'; import { namespaces } from '../../utils/namespaces';
import map_children from './shared/map_children'; import map_children from './shared/map_children';
import { is_name_contenteditable, get_contenteditable_attr, has_contenteditable_attr } from '../utils/contenteditable'; import {
import { regex_dimensions, regex_starts_with_newline, regex_non_whitespace_character, regex_box_size } from '../../utils/patterns'; is_name_contenteditable,
get_contenteditable_attr,
has_contenteditable_attr
} from '../utils/contenteditable';
import {
regex_dimensions,
regex_starts_with_newline,
regex_non_whitespace_character,
regex_box_size
} from '../../utils/patterns';
import fuzzymatch from '../../utils/fuzzymatch'; import fuzzymatch from '../../utils/fuzzymatch';
import list from '../../utils/list'; import list from '../../utils/list';
import hash from '../utils/hash'; import hash from '../utils/hash';
@ -25,10 +34,31 @@ import { string_literal } from '../utils/stringify';
import { Literal } from 'estree'; import { Literal } from 'estree';
import compiler_warnings from '../compiler_warnings'; import compiler_warnings from '../compiler_warnings';
import compiler_errors from '../compiler_errors'; import compiler_errors from '../compiler_errors';
import { ARIARoleDefinitionKey, roles, aria, ARIAPropertyDefinition, ARIAProperty } from 'aria-query'; import {
import { is_interactive_element, is_non_interactive_element, is_non_interactive_roles, is_presentation_role, is_interactive_roles, is_hidden_from_screen_reader, is_semantic_role_element, is_abstract_role, is_static_element, has_disabled_attribute, is_valid_autocomplete } from '../utils/a11y'; ARIARoleDefinitionKey,
roles,
const aria_attributes = 'activedescendant atomic autocomplete busy checked colcount colindex colspan controls current describedby description details disabled dropeffect errormessage expanded flowto grabbed haspopup hidden invalid keyshortcuts label labelledby level live modal multiline multiselectable orientation owns placeholder posinset pressed readonly relevant required roledescription rowcount rowindex rowspan selected setsize sort valuemax valuemin valuenow valuetext'.split(' '); aria,
ARIAPropertyDefinition,
ARIAProperty
} from 'aria-query';
import {
is_interactive_element,
is_non_interactive_element,
is_non_interactive_roles,
is_presentation_role,
is_interactive_roles,
is_hidden_from_screen_reader,
is_semantic_role_element,
is_abstract_role,
is_static_element,
has_disabled_attribute,
is_valid_autocomplete
} from '../utils/a11y';
const aria_attributes =
'activedescendant atomic autocomplete busy checked colcount colindex colspan controls current describedby description details disabled dropeffect errormessage expanded flowto grabbed haspopup hidden invalid keyshortcuts label labelledby level live modal multiline multiselectable orientation owns placeholder posinset pressed readonly relevant required roledescription rowcount rowindex rowspan selected setsize sort valuemax valuemin valuenow valuetext'.split(
' '
);
const aria_attribute_set = new Set(aria_attributes); const aria_attribute_set = new Set(aria_attributes);
const aria_roles = roles.keys(); const aria_roles = roles.keys();
@ -47,10 +77,7 @@ const a11y_required_attributes = {
object: ['title', 'aria-label', 'aria-labelledby'] object: ['title', 'aria-label', 'aria-labelledby']
}; };
const a11y_distracting_elements = new Set([ const a11y_distracting_elements = new Set(['blink', 'marquee']);
'blink',
'marquee'
]);
const a11y_required_content = new Set([ const a11y_required_content = new Set([
// anchor-has-content // anchor-has-content
@ -190,24 +217,8 @@ const input_type_to_implicit_role = new Map([
* TODO make this configurable by the user * TODO make this configurable by the user
*/ */
const a11y_non_interactive_element_to_interactive_role_exceptions = { const a11y_non_interactive_element_to_interactive_role_exceptions = {
ul: [ ul: ['listbox', 'menu', 'menubar', 'radiogroup', 'tablist', 'tree', 'treegrid'],
'listbox', ol: ['listbox', 'menu', 'menubar', 'radiogroup', 'tablist', 'tree', 'treegrid'],
'menu',
'menubar',
'radiogroup',
'tablist',
'tree',
'treegrid'
],
ol: [
'listbox',
'menu',
'menubar',
'radiogroup',
'tablist',
'tree',
'treegrid'
],
li: ['menuitem', 'option', 'row', 'tab', 'treeitem'], li: ['menuitem', 'option', 'row', 'tab', 'treeitem'],
table: ['grid'], table: ['grid'],
td: ['gridcell'], td: ['gridcell'],
@ -237,7 +248,10 @@ function menuitem_implicit_role(attribute_map: Map<string, Attribute>) {
return menuitem_type_to_implicit_role.get(type); return menuitem_type_to_implicit_role.get(type);
} }
function get_implicit_role(name: string, attribute_map: Map<string, Attribute>) : (string | undefined) { function get_implicit_role(
name: string,
attribute_map: Map<string, Attribute>
): string | undefined {
if (name === 'menuitem') { if (name === 'menuitem') {
return menuitem_implicit_role(attribute_map); return menuitem_implicit_role(attribute_map);
} else if (name === 'input') { } else if (name === 'input') {
@ -261,13 +275,7 @@ const valid_modifiers = new Set([
'trusted' 'trusted'
]); ]);
const passive_events = new Set([ const passive_events = new Set(['wheel', 'touchstart', 'touchmove', 'touchend', 'touchcancel']);
'wheel',
'touchstart',
'touchmove',
'touchend',
'touchcancel'
]);
const react_attributes = new Map([ const react_attributes = new Map([
['className', 'class'], ['className', 'class'],
@ -297,9 +305,7 @@ function get_namespace(parent: Element, element: Element, explicit_namespace: st
const parent_element = parent.find_nearest(/^Element/); const parent_element = parent.find_nearest(/^Element/);
if (!parent_element) { if (!parent_element) {
return explicit_namespace || (is_svg(element.name) return explicit_namespace || (is_svg(element.name) ? namespaces.svg : null);
? namespaces.svg
: null);
} }
if (parent_element.namespace !== namespaces.foreign) { if (parent_element.namespace !== namespaces.foreign) {
@ -310,7 +316,10 @@ function get_namespace(parent: Element, element: Element, explicit_namespace: st
return parent_element.namespace; return parent_element.namespace;
} }
function is_valid_aria_attribute_value(schema: ARIAPropertyDefinition, value: string | boolean): boolean { function is_valid_aria_attribute_value(
schema: ARIAPropertyDefinition,
value: string | boolean
): boolean {
switch (schema.type) { switch (schema.type) {
case 'boolean': case 'boolean':
return typeof value === 'boolean'; return typeof value === 'boolean';
@ -323,14 +332,21 @@ function is_valid_aria_attribute_value(schema: ARIAPropertyDefinition, value: st
case 'number': case 'number':
return typeof value !== 'boolean' && isNaN(Number(value)) === false; return typeof value !== 'boolean' && isNaN(Number(value)) === false;
case 'token': // single token case 'token': // single token
return (schema.values || []) return (
.indexOf(typeof value === 'string' ? value.toLowerCase() : value) > -1; (schema.values || []).indexOf(typeof value === 'string' ? value.toLowerCase() : value) > -1
);
case 'idlist': // if list of ids, split each case 'idlist': // if list of ids, split each
return typeof value === 'string' return (
&& value.split(regex_any_repeated_whitespaces).every((id) => typeof id === 'string'); typeof value === 'string' &&
value.split(regex_any_repeated_whitespaces).every((id) => typeof id === 'string')
);
case 'tokenlist': // if list of tokens, split each case 'tokenlist': // if list of tokens, split each
return typeof value === 'string' return (
&& value.split(regex_any_repeated_whitespaces).every((token) => (schema.values || []).indexOf(token.toLowerCase()) > -1); typeof value === 'string' &&
value
.split(regex_any_repeated_whitespaces)
.every((token) => (schema.values || []).indexOf(token.toLowerCase()) > -1)
);
default: default:
return false; return false;
} }
@ -398,7 +414,7 @@ export default class Element extends Node {
if (this.name === 'textarea') { if (this.name === 'textarea') {
if (info.children.length > 0) { if (info.children.length > 0) {
const value_attribute = info.attributes.find(node => node.name === 'value'); const value_attribute = info.attributes.find((node) => node.name === 'value');
if (value_attribute) { if (value_attribute) {
component.error(value_attribute, compiler_errors.textarea_duplicate_value); component.error(value_attribute, compiler_errors.textarea_duplicate_value);
return; return;
@ -420,7 +436,7 @@ export default class Element extends Node {
// Special case — treat these the same way: // Special case — treat these the same way:
// <option>{foo}</option> // <option>{foo}</option>
// <option value={foo}>{foo}</option> // <option value={foo}>{foo}</option>
const value_attribute = info.attributes.find(attribute => attribute.name === 'value'); const value_attribute = info.attributes.find((attribute) => attribute.name === 'value');
if (!value_attribute) { if (!value_attribute) {
info.attributes.push({ info.attributes.push({
@ -432,7 +448,7 @@ export default class Element extends Node {
} }
} }
} }
const has_let = info.attributes.some(node => node.type === 'Let'); const has_let = info.attributes.some((node) => node.type === 'Let');
if (has_let) { if (has_let) {
scope = scope.child(); scope = scope.child();
} }
@ -441,7 +457,7 @@ export default class Element extends Node {
const order = ['Binding']; // everything else is -1 const order = ['Binding']; // everything else is -1
info.attributes.sort((a, b) => order.indexOf(a.type) - order.indexOf(b.type)); info.attributes.sort((a, b) => order.indexOf(a.type) - order.indexOf(b.type));
info.attributes.forEach(node => { info.attributes.forEach((node) => {
switch (node.type) { switch (node.type) {
case 'Action': case 'Action':
this.actions.push(new Action(component, this, scope, node)); this.actions.push(new Action(component, this, scope, node));
@ -476,14 +492,13 @@ export default class Element extends Node {
this.lets.push(l); this.lets.push(l);
const dependencies = new Set([l.name.name]); const dependencies = new Set([l.name.name]);
l.names.forEach(name => { l.names.forEach((name) => {
scope.add(name, dependencies, this); scope.add(name, dependencies, this);
}); });
break; break;
} }
case 'Transition': case 'Transition': {
{
const transition = new Transition(component, this, scope, node); const transition = new Transition(component, this, scope, node);
if (node.intro) this.intro = transition; if (node.intro) this.intro = transition;
if (node.outro) this.outro = transition; if (node.outro) this.outro = transition;
@ -509,11 +524,13 @@ export default class Element extends Node {
component.apply_stylesheet(this); component.apply_stylesheet(this);
if (this.parent) { if (this.parent) {
if (this.actions.length > 0 || if (
this.actions.length > 0 ||
this.animation || this.animation ||
this.bindings.length > 0 || this.bindings.length > 0 ||
this.classes.length > 0 || this.classes.length > 0 ||
this.intro || this.outro || this.intro ||
this.outro ||
this.handlers.length > 0 || this.handlers.length > 0 ||
this.styles.length > 0 || this.styles.length > 0 ||
this.name === 'option' || this.name === 'option' ||
@ -529,7 +546,12 @@ export default class Element extends Node {
} }
validate() { validate() {
if (this.component.var_lookup.has(this.name) && this.component.var_lookup.get(this.name).imported && !is_svg(this.name) && !is_html(this.name)) { if (
this.component.var_lookup.has(this.name) &&
this.component.var_lookup.get(this.name).imported &&
!is_svg(this.name) &&
!is_html(this.name)
) {
this.component.warn(this, compiler_warnings.component_name_lowercase(this.name)); this.component.warn(this, compiler_warnings.component_name_lowercase(this.name));
} }
@ -543,13 +565,12 @@ export default class Element extends Node {
this.validate_bindings(); this.validate_bindings();
this.validate_content(); this.validate_content();
} }
} }
validate_attributes() { validate_attributes() {
const { component, parent } = this; const { component, parent } = this;
this.attributes.forEach(attribute => { this.attributes.forEach((attribute) => {
if (attribute.is_spread) return; if (attribute.is_spread) return;
const name = attribute.name.toLowerCase(); const name = attribute.name.toLowerCase();
@ -585,7 +606,13 @@ export default class Element extends Node {
} }
if (react_attributes.has(attribute.name)) { if (react_attributes.has(attribute.name)) {
component.warn(attribute, compiler_warnings.invalid_html_attribute(attribute.name, react_attributes.get(attribute.name))); component.warn(
attribute,
compiler_warnings.invalid_html_attribute(
attribute.name,
react_attributes.get(attribute.name)
)
);
} }
} }
}); });
@ -597,14 +624,10 @@ export default class Element extends Node {
const attribute_map = new Map<string, Attribute>(); const attribute_map = new Map<string, Attribute>();
const handlers_map = new Map(); const handlers_map = new Map();
attributes.forEach(attribute => ( attributes.forEach((attribute) => attribute_map.set(attribute.name, attribute));
attribute_map.set(attribute.name, attribute) handlers.forEach((handler) => handlers_map.set(handler.name, handler));
));
handlers.forEach(handler => (
handlers_map.set(handler.name, handler)
));
attributes.forEach(attribute => { attributes.forEach((attribute) => {
if (attribute.is_spread) return; if (attribute.is_spread) return;
const name = attribute.name.toLowerCase(); const name = attribute.name.toLowerCase();
@ -634,12 +657,20 @@ export default class Element extends Node {
if (value !== null && value !== undefined && aria.has(name as ARIAProperty)) { if (value !== null && value !== undefined && aria.has(name as ARIAProperty)) {
const schema = aria.get(name as ARIAProperty); const schema = aria.get(name as ARIAProperty);
if (!is_valid_aria_attribute_value(schema, value)) { if (!is_valid_aria_attribute_value(schema, value)) {
component.warn(attribute, compiler_warnings.a11y_incorrect_attribute_type(schema, name)); component.warn(
attribute,
compiler_warnings.a11y_incorrect_attribute_type(schema, name)
);
} }
} }
// aria-activedescendant-has-tabindex // aria-activedescendant-has-tabindex
if (name === 'aria-activedescendant' && !this.is_dynamic_element && !is_interactive_element(this.name, attribute_map) && !attribute_map.has('tabindex')) { if (
name === 'aria-activedescendant' &&
!this.is_dynamic_element &&
!is_interactive_element(this.name, attribute_map) &&
!attribute_map.has('tabindex')
) {
component.warn(attribute, compiler_warnings.a11y_aria_activedescendant_has_tabindex); component.warn(attribute, compiler_warnings.a11y_aria_activedescendant_has_tabindex);
} }
} }
@ -654,7 +685,9 @@ export default class Element extends Node {
const value = attribute.get_static_value(); const value = attribute.get_static_value();
if (typeof value === 'string') { if (typeof value === 'string') {
value.split(regex_any_repeated_whitespaces).forEach((current_role: ARIARoleDefinitionKey) => { value
.split(regex_any_repeated_whitespaces)
.forEach((current_role: ARIARoleDefinitionKey) => {
if (current_role && is_abstract_role(current_role)) { if (current_role && is_abstract_role(current_role)) {
component.warn(attribute, compiler_warnings.a11y_no_abstract_role(current_role)); component.warn(attribute, compiler_warnings.a11y_no_abstract_role(current_role));
} else if (current_role && !aria_role_set.has(current_role)) { } else if (current_role && !aria_role_set.has(current_role)) {
@ -670,21 +703,36 @@ export default class Element extends Node {
// Footers and headers are special cases, and should not have redundant roles unless they are the children of sections or articles. // Footers and headers are special cases, and should not have redundant roles unless they are the children of sections or articles.
const is_parent_section_or_article = is_parent(this.parent, ['section', 'article']); const is_parent_section_or_article = is_parent(this.parent, ['section', 'article']);
if (!is_parent_section_or_article) { if (!is_parent_section_or_article) {
const has_nested_redundant_role = current_role === a11y_nested_implicit_semantics.get(this.name); const has_nested_redundant_role =
current_role === a11y_nested_implicit_semantics.get(this.name);
if (has_nested_redundant_role) { if (has_nested_redundant_role) {
component.warn(attribute, compiler_warnings.a11y_no_redundant_roles(current_role)); component.warn(
attribute,
compiler_warnings.a11y_no_redundant_roles(current_role)
);
} }
} }
// role-has-required-aria-props // role-has-required-aria-props
if (!this.is_dynamic_element && !is_semantic_role_element(current_role, this.name, attribute_map)) { if (
!this.is_dynamic_element &&
!is_semantic_role_element(current_role, this.name, attribute_map)
) {
const role = roles.get(current_role); const role = roles.get(current_role);
if (role) { if (role) {
const required_role_props = Object.keys(role.requiredProps); const required_role_props = Object.keys(role.requiredProps);
const has_missing_props = required_role_props.some(prop => !attributes.find(a => a.name === prop)); const has_missing_props = required_role_props.some(
(prop) => !attributes.find((a) => a.name === prop)
);
if (has_missing_props) { if (has_missing_props) {
component.warn(attribute, compiler_warnings.a11y_role_has_required_aria_props(current_role, required_role_props)); component.warn(
attribute,
compiler_warnings.a11y_role_has_required_aria_props(
current_role,
required_role_props
)
);
} }
} }
} }
@ -698,20 +746,46 @@ export default class Element extends Node {
is_static_element(this.name, attribute_map) && is_static_element(this.name, attribute_map) &&
!attribute_map.get('tabindex') !attribute_map.get('tabindex')
) { ) {
const has_interactive_handlers = handlers.some((handler) => a11y_interactive_handlers.has(handler.name)); const has_interactive_handlers = handlers.some((handler) =>
a11y_interactive_handlers.has(handler.name)
);
if (has_interactive_handlers) { if (has_interactive_handlers) {
component.warn(this, compiler_warnings.a11y_interactive_supports_focus(current_role)); component.warn(
this,
compiler_warnings.a11y_interactive_supports_focus(current_role)
);
} }
} }
// no-interactive-element-to-noninteractive-role // no-interactive-element-to-noninteractive-role
if (is_interactive_element(this.name, attribute_map) && (is_non_interactive_roles(current_role) || is_presentation_role(current_role))) { if (
component.warn(this, compiler_warnings.a11y_no_interactive_element_to_noninteractive_role(current_role, this.name)); is_interactive_element(this.name, attribute_map) &&
(is_non_interactive_roles(current_role) || is_presentation_role(current_role))
) {
component.warn(
this,
compiler_warnings.a11y_no_interactive_element_to_noninteractive_role(
current_role,
this.name
)
);
} }
// no-noninteractive-element-to-interactive-role // no-noninteractive-element-to-interactive-role
if (is_non_interactive_element(this.name, attribute_map) && is_interactive_roles(current_role) && !a11y_non_interactive_element_to_interactive_role_exceptions[this.name]?.includes(current_role)) { if (
component.warn(this, compiler_warnings.a11y_no_noninteractive_element_to_interactive_role(current_role, this.name)); is_non_interactive_element(this.name, attribute_map) &&
is_interactive_roles(current_role) &&
!a11y_non_interactive_element_to_interactive_role_exceptions[this.name]?.includes(
current_role
)
) {
component.warn(
this,
compiler_warnings.a11y_no_noninteractive_element_to_interactive_role(
current_role,
this.name
)
);
} }
}); });
} }
@ -745,35 +819,37 @@ export default class Element extends Node {
// click-events-have-key-events // click-events-have-key-events
if (handlers_map.has('click')) { if (handlers_map.has('click')) {
const role = attribute_map.get('role'); const role = attribute_map.get('role');
const is_non_presentation_role = role?.is_static && !is_presentation_role(role.get_static_value() as ARIARoleDefinitionKey); const is_non_presentation_role =
role?.is_static && !is_presentation_role(role.get_static_value() as ARIARoleDefinitionKey);
if ( if (
!this.is_dynamic_element && !this.is_dynamic_element &&
!is_hidden_from_screen_reader(this.name, attribute_map) && !is_hidden_from_screen_reader(this.name, attribute_map) &&
(!role || is_non_presentation_role) && (!role || is_non_presentation_role) &&
!is_interactive_element(this.name, attribute_map) && !is_interactive_element(this.name, attribute_map) &&
!this.attributes.find(attr => attr.is_spread) !this.attributes.find((attr) => attr.is_spread)
) { ) {
const has_key_event = const has_key_event =
handlers_map.has('keydown') || handlers_map.has('keydown') || handlers_map.has('keyup') || handlers_map.has('keypress');
handlers_map.has('keyup') ||
handlers_map.has('keypress');
if (!has_key_event) { if (!has_key_event) {
component.warn( component.warn(this, compiler_warnings.a11y_click_events_have_key_events);
this,
compiler_warnings.a11y_click_events_have_key_events
);
} }
} }
} }
const role = attribute_map.get('role'); const role = attribute_map.get('role');
const role_static_value = role?.get_static_value() as ARIARoleDefinitionKey; const role_static_value = role?.get_static_value() as ARIARoleDefinitionKey;
const role_value = (role ? role_static_value : get_implicit_role(this.name, attribute_map)) as ARIARoleDefinitionKey; const role_value = (
role ? role_static_value : get_implicit_role(this.name, attribute_map)
) as ARIARoleDefinitionKey;
// no-noninteractive-tabindex // no-noninteractive-tabindex
if (!this.is_dynamic_element && !is_interactive_element(this.name, attribute_map) && !is_interactive_roles(role_static_value)) { if (
!this.is_dynamic_element &&
!is_interactive_element(this.name, attribute_map) &&
!is_interactive_roles(role_static_value)
) {
const tab_index = attribute_map.get('tabindex'); const tab_index = attribute_map.get('tabindex');
if (tab_index && (!tab_index.is_static || Number(tab_index.get_static_value()) >= 0)) { if (tab_index && (!tab_index.is_static || Number(tab_index.get_static_value()) >= 0)) {
component.warn(this, compiler_warnings.a11y_no_noninteractive_tabindex); component.warn(this, compiler_warnings.a11y_no_noninteractive_tabindex);
@ -783,14 +859,22 @@ export default class Element extends Node {
// role-supports-aria-props // role-supports-aria-props
if (typeof role_value === 'string' && roles.has(role_value)) { if (typeof role_value === 'string' && roles.has(role_value)) {
const { props } = roles.get(role_value); const { props } = roles.get(role_value);
const invalid_aria_props = new Set(aria.keys().filter(attribute => !(attribute in props))); const invalid_aria_props = new Set(aria.keys().filter((attribute) => !(attribute in props)));
const is_implicit = role_value && role === undefined; const is_implicit = role_value && role === undefined;
attributes attributes
.filter(prop => prop.type !== 'Spread') .filter((prop) => prop.type !== 'Spread')
.forEach(prop => { .forEach((prop) => {
if (invalid_aria_props.has(prop.name as ARIAProperty)) { if (invalid_aria_props.has(prop.name as ARIAProperty)) {
component.warn(prop, compiler_warnings.a11y_role_supports_aria_props(prop.name, role_value, is_implicit, this.name)); component.warn(
prop,
compiler_warnings.a11y_role_supports_aria_props(
prop.name,
role_value,
is_implicit,
this.name
)
);
} }
}); });
} }
@ -804,9 +888,14 @@ export default class Element extends Node {
is_non_interactive_roles(role_static_value)) || is_non_interactive_roles(role_static_value)) ||
(is_non_interactive_element(this.name, attribute_map) && !role)) (is_non_interactive_element(this.name, attribute_map) && !role))
) { ) {
const has_interactive_handlers = handlers.some((handler) => a11y_recommended_interactive_handlers.has(handler.name)); const has_interactive_handlers = handlers.some((handler) =>
a11y_recommended_interactive_handlers.has(handler.name)
);
if (has_interactive_handlers) { if (has_interactive_handlers) {
component.warn(this, compiler_warnings.a11y_no_noninteractive_element_interactions(this.name)); component.warn(
this,
compiler_warnings.a11y_no_noninteractive_element_interactions(this.name)
);
} }
} }
@ -841,13 +930,9 @@ export default class Element extends Node {
const attribute_map = new Map(); const attribute_map = new Map();
const handlers_map = new Map(); const handlers_map = new Map();
attributes.forEach(attribute => ( attributes.forEach((attribute) => attribute_map.set(attribute.name, attribute));
attribute_map.set(attribute.name, attribute)
));
handlers.forEach(handler => ( handlers.forEach((handler) => handlers_map.set(handler.name, handler));
handlers_map.set(handler.name, handler)
));
if (this.name === 'a') { if (this.name === 'a') {
const href_attribute = attribute_map.get('href') || attribute_map.get('xlink:href'); const href_attribute = attribute_map.get('href') || attribute_map.get('xlink:href');
@ -859,13 +944,22 @@ export default class Element extends Node {
// links with target="_blank" should have noopener or noreferrer: https://developer.chrome.com/docs/lighthouse/best-practices/external-anchors-use-rel-noopener/ // links with target="_blank" should have noopener or noreferrer: https://developer.chrome.com/docs/lighthouse/best-practices/external-anchors-use-rel-noopener/
// modern browsers add noopener by default, so we only need to check legacy browsers // modern browsers add noopener by default, so we only need to check legacy browsers
// legacy browsers don't support noopener so we only check for noreferrer there // legacy browsers don't support noopener so we only check for noreferrer there
if (component.compile_options.legacy && target_attribute && target_attribute.get_static_value() === '_blank' && href_attribute) { if (
const href_static_value = href_attribute.get_static_value() ? href_attribute.get_static_value().toLowerCase() : null; component.compile_options.legacy &&
target_attribute &&
target_attribute.get_static_value() === '_blank' &&
href_attribute
) {
const href_static_value = href_attribute.get_static_value()
? href_attribute.get_static_value().toLowerCase()
: null;
if (href_static_value === null || href_static_value.match(/^(https?:)?\/\//i)) { if (href_static_value === null || href_static_value.match(/^(https?:)?\/\//i)) {
const rel = attribute_map.get('rel'); const rel = attribute_map.get('rel');
if (rel == null || rel.is_static) { if (rel == null || rel.is_static) {
const rel_values = rel ? rel.get_static_value().split(regex_any_repeated_whitespaces) : []; const rel_values = rel
? rel.get_static_value().split(regex_any_repeated_whitespaces)
: [];
if (!rel || !rel_values.includes('noreferrer')) { if (!rel || !rel_values.includes('noreferrer')) {
component.warn(this, { component.warn(this, {
code: 'security-anchor-rel-noreferrer', code: 'security-anchor-rel-noreferrer',
@ -888,7 +982,10 @@ export default class Element extends Node {
const href_value = href_attribute.get_static_value(); const href_value = href_attribute.get_static_value();
if (href_value === '' || href_value === '#' || /^\W*javascript:/i.test(href_value)) { if (href_value === '' || href_value === '#' || /^\W*javascript:/i.test(href_value)) {
component.warn(href_attribute, compiler_warnings.a11y_invalid_attribute(href_attribute.name, href_value)); component.warn(
href_attribute,
compiler_warnings.a11y_invalid_attribute(href_attribute.name, href_value)
);
} }
} else { } else {
const id_attribute_valid = id_attribute && id_attribute.get_static_value() !== ''; const id_attribute_valid = id_attribute && id_attribute.get_static_value() !== '';
@ -901,7 +998,7 @@ export default class Element extends Node {
} else { } else {
const required_attributes = a11y_required_attributes[this.name]; const required_attributes = a11y_required_attributes[this.name];
if (required_attributes) { if (required_attributes) {
const has_attribute = required_attributes.some(name => attribute_map.has(name)); const has_attribute = required_attributes.some((name) => attribute_map.has(name));
if (!has_attribute) { if (!has_attribute) {
should_have_attribute(this, required_attributes); should_have_attribute(this, required_attributes);
@ -913,7 +1010,7 @@ export default class Element extends Node {
const type = attribute_map.get('type'); const type = attribute_map.get('type');
if (type && type.get_static_value() === 'image') { if (type && type.get_static_value() === 'image') {
const required_attributes = ['alt', 'aria-label', 'aria-labelledby']; const required_attributes = ['alt', 'aria-label', 'aria-labelledby'];
const has_attribute = required_attributes.some(name => attribute_map.has(name)); const has_attribute = required_attributes.some((name) => attribute_map.has(name));
if (!has_attribute) { if (!has_attribute) {
should_have_attribute(this, required_attributes, 'input type="image"'); should_have_attribute(this, required_attributes, 'input type="image"');
@ -928,7 +1025,10 @@ export default class Element extends Node {
const autocomplete_value = autocomplete.get_static_value(); const autocomplete_value = autocomplete.get_static_value();
if (!is_valid_autocomplete(autocomplete_value)) { if (!is_valid_autocomplete(autocomplete_value)) {
component.warn(autocomplete, compiler_warnings.a11y_autocomplete_valid(type_value, autocomplete_value)); component.warn(
autocomplete,
compiler_warnings.a11y_autocomplete_valid(type_value, autocomplete_value)
);
} }
} }
} }
@ -950,7 +1050,12 @@ export default class Element extends Node {
if (this.name === 'label') { if (this.name === 'label') {
const has_input_child = (children: INode[]) => { const has_input_child = (children: INode[]) => {
if (children.some(child => (child instanceof Element && (a11y_labelable.has(child.name) || child.name === 'slot')))) { if (
children.some(
(child) =>
child instanceof Element && (a11y_labelable.has(child.name) || child.name === 'slot')
)
) {
return true; return true;
} }
@ -982,7 +1087,9 @@ export default class Element extends Node {
let has_caption; let has_caption;
const track = this.children.find((i: Element) => i.name === 'track'); const track = this.children.find((i: Element) => i.name === 'track');
if (track) { if (track) {
has_caption = track.attributes.find(a => a.name === 'kind' && a.get_static_value() === 'captions'); has_caption = track.attributes.find(
(a) => a.name === 'kind' && a.get_static_value() === 'captions'
);
} }
if (!has_caption) { if (!has_caption) {
@ -1016,21 +1123,24 @@ export default class Element extends Node {
} }
if (this.name === 'figure') { if (this.name === 'figure') {
const children = this.children.filter(node => { const children = this.children.filter((node) => {
if (node.type === 'Comment') return false; if (node.type === 'Comment') return false;
if (node.type === 'Text') return regex_non_whitespace_character.test(node.data); if (node.type === 'Text') return regex_non_whitespace_character.test(node.data);
return true; return true;
}); });
const index = children.findIndex(child => (child as Element).name === 'figcaption'); const index = children.findIndex((child) => (child as Element).name === 'figcaption');
if (index !== -1 && (index !== 0 && index !== children.length - 1)) { if (index !== -1 && index !== 0 && index !== children.length - 1) {
component.warn(children[index], compiler_warnings.a11y_structure_first_or_last); component.warn(children[index], compiler_warnings.a11y_structure_first_or_last);
} }
} }
if (handlers_map.has('mouseover') && !handlers_map.has('focus')) { if (handlers_map.has('mouseover') && !handlers_map.has('focus')) {
component.warn(this, compiler_warnings.a11y_mouse_events_have_key_events('mouseover', 'focus')); component.warn(
this,
compiler_warnings.a11y_mouse_events_have_key_events('mouseover', 'focus')
);
} }
if (handlers_map.has('mouseout') && !handlers_map.has('blur')) { if (handlers_map.has('mouseout') && !handlers_map.has('blur')) {
@ -1039,7 +1149,7 @@ export default class Element extends Node {
} }
validate_bindings_foreign() { validate_bindings_foreign() {
this.bindings.forEach(binding => { this.bindings.forEach((binding) => {
if (binding.name !== 'this') { if (binding.name !== 'this') {
return this.component.error(binding, compiler_errors.invalid_binding_foreign(binding.name)); return this.component.error(binding, compiler_errors.invalid_binding_foreign(binding.name));
} }
@ -1050,9 +1160,7 @@ export default class Element extends Node {
const { component } = this; const { component } = this;
const check_type_attribute = () => { const check_type_attribute = () => {
const attribute = this.attributes.find( const attribute = this.attributes.find((attribute: Attribute) => attribute.name === 'type');
(attribute: Attribute) => attribute.name === 'type'
);
if (!attribute) return null; if (!attribute) return null;
@ -1069,16 +1177,15 @@ export default class Element extends Node {
return value; return value;
}; };
this.bindings.forEach(binding => { this.bindings.forEach((binding) => {
const { name } = binding; const { name } = binding;
if (name === 'value') { if (name === 'value') {
if ( if (this.name !== 'input' && this.name !== 'textarea' && this.name !== 'select') {
this.name !== 'input' && return component.error(
this.name !== 'textarea' && binding,
this.name !== 'select' compiler_errors.invalid_binding_elements(this.name, 'value')
) { );
return component.error(binding, compiler_errors.invalid_binding_elements(this.name, 'value'));
} }
if (this.name === 'select') { if (this.name === 'select') {
@ -1094,38 +1201,61 @@ export default class Element extends Node {
} }
} else if (name === 'checked' || name === 'indeterminate') { } else if (name === 'checked' || name === 'indeterminate') {
if (this.name !== 'input') { if (this.name !== 'input') {
return component.error(binding, compiler_errors.invalid_binding_elements(this.name, name)); return component.error(
binding,
compiler_errors.invalid_binding_elements(this.name, name)
);
} }
const type = check_type_attribute(); const type = check_type_attribute();
if (type !== 'checkbox') { if (type !== 'checkbox') {
return component.error(binding, compiler_errors.invalid_binding_no_checkbox(name, type === 'radio')); return component.error(
binding,
compiler_errors.invalid_binding_no_checkbox(name, type === 'radio')
);
} }
} else if (name === 'group') { } else if (name === 'group') {
if (this.name !== 'input') { if (this.name !== 'input') {
return component.error(binding, compiler_errors.invalid_binding_elements(this.name, 'group')); return component.error(
binding,
compiler_errors.invalid_binding_elements(this.name, 'group')
);
} }
const type = check_type_attribute(); const type = check_type_attribute();
if (type !== 'checkbox' && type !== 'radio') { if (type !== 'checkbox' && type !== 'radio') {
return component.error(binding, compiler_errors.invalid_binding_element_with('<input type="checkbox"> or <input type="radio">', 'group')); return component.error(
binding,
compiler_errors.invalid_binding_element_with(
'<input type="checkbox"> or <input type="radio">',
'group'
)
);
} }
} else if (name === 'files') { } else if (name === 'files') {
if (this.name !== 'input') { if (this.name !== 'input') {
return component.error(binding, compiler_errors.invalid_binding_elements(this.name, 'files')); return component.error(
binding,
compiler_errors.invalid_binding_elements(this.name, 'files')
);
} }
const type = check_type_attribute(); const type = check_type_attribute();
if (type !== 'file') { if (type !== 'file') {
return component.error(binding, compiler_errors.invalid_binding_element_with('<input type="file">', 'files')); return component.error(
binding,
compiler_errors.invalid_binding_element_with('<input type="file">', 'files')
);
} }
} else if (name === 'open') { } else if (name === 'open') {
if (this.name !== 'details') { if (this.name !== 'details') {
return component.error(binding, compiler_errors.invalid_binding_element_with('<details>', name)); return component.error(
binding,
compiler_errors.invalid_binding_element_with('<details>', name)
);
} }
} else if ( } else if (
name === 'currentTime' || name === 'currentTime' ||
@ -1142,41 +1272,59 @@ export default class Element extends Node {
name === 'readyState' name === 'readyState'
) { ) {
if (this.name !== 'audio' && this.name !== 'video') { if (this.name !== 'audio' && this.name !== 'video') {
return component.error(binding, compiler_errors.invalid_binding_element_with('audio> or <video>', name)); return component.error(
binding,
compiler_errors.invalid_binding_element_with('audio> or <video>', name)
);
} }
} else if ( } else if (name === 'videoHeight' || name === 'videoWidth') {
name === 'videoHeight' ||
name === 'videoWidth'
) {
if (this.name !== 'video') { if (this.name !== 'video') {
return component.error(binding, compiler_errors.invalid_binding_element_with('<video>', name)); return component.error(
binding,
compiler_errors.invalid_binding_element_with('<video>', name)
);
} }
} else if (regex_dimensions.test(name)) { } else if (regex_dimensions.test(name)) {
if (this.name === 'svg' && (name === 'offsetWidth' || name === 'offsetHeight')) { if (this.name === 'svg' && (name === 'offsetWidth' || name === 'offsetHeight')) {
return component.error(binding, compiler_errors.invalid_binding_on(binding.name, `<svg>. Use '${name.replace('offset', 'client')}' instead`)); return component.error(
binding,
compiler_errors.invalid_binding_on(
binding.name,
`<svg>. Use '${name.replace('offset', 'client')}' instead`
)
);
} else if (is_svg(this.name)) { } else if (is_svg(this.name)) {
return component.error(binding, compiler_errors.invalid_binding_on(binding.name, 'SVG elements')); return component.error(
binding,
compiler_errors.invalid_binding_on(binding.name, 'SVG elements')
);
} else if (is_void(this.name)) { } else if (is_void(this.name)) {
return component.error(binding, compiler_errors.invalid_binding_on(binding.name, `void elements like <${this.name}>. Use a wrapper element instead`)); return component.error(
binding,
compiler_errors.invalid_binding_on(
binding.name,
`void elements like <${this.name}>. Use a wrapper element instead`
)
);
} }
} else if ( } else if (name === 'naturalWidth' || name === 'naturalHeight') {
name === 'naturalWidth' ||
name === 'naturalHeight'
) {
if (this.name !== 'img') { if (this.name !== 'img') {
return component.error(binding, compiler_errors.invalid_binding_element_with('<img>', name)); return component.error(
binding,
compiler_errors.invalid_binding_element_with('<img>', name)
);
} }
} else if (is_name_contenteditable(name)) { } else if (is_name_contenteditable(name)) {
const contenteditable = get_contenteditable_attr(this); const contenteditable = get_contenteditable_attr(this);
if (!contenteditable) { if (!contenteditable) {
return component.error(binding, compiler_errors.missing_contenteditable_attribute); return component.error(binding, compiler_errors.missing_contenteditable_attribute);
} else if (contenteditable && !contenteditable.is_static) { } else if (contenteditable && !contenteditable.is_static) {
return component.error(contenteditable, compiler_errors.dynamic_contenteditable_attribute); return component.error(
contenteditable,
compiler_errors.dynamic_contenteditable_attribute
);
} }
} else if ( } else if (name !== 'this' && !regex_box_size.test(name)) {
name !== 'this' &&
!regex_box_size.test(name)
) {
return component.error(binding, compiler_errors.invalid_binding(binding.name)); return component.error(binding, compiler_errors.invalid_binding(binding.name));
} }
}); });
@ -1185,10 +1333,8 @@ export default class Element extends Node {
validate_content() { validate_content() {
if (!a11y_required_content.has(this.name)) return; if (!a11y_required_content.has(this.name)) return;
if (this.contains_a11y_label) return; if (this.contains_a11y_label) return;
if ( if (this.bindings.some((binding) => ['textContent', 'innerHTML'].includes(binding.name)))
this.bindings return;
.some((binding) => ['textContent', 'innerHTML'].includes(binding.name))
) return;
if (this.children.length === 0) { if (this.children.length === 0) {
this.component.warn(this, compiler_warnings.a11y_missing_content(this.name)); this.component.warn(this, compiler_warnings.a11y_missing_content(this.name));
@ -1198,18 +1344,27 @@ export default class Element extends Node {
validate_event_handlers() { validate_event_handlers() {
const { component } = this; const { component } = this;
this.handlers.forEach(handler => { this.handlers.forEach((handler) => {
if (handler.modifiers.has('passive') && handler.modifiers.has('preventDefault')) { if (handler.modifiers.has('passive') && handler.modifiers.has('preventDefault')) {
return component.error(handler, compiler_errors.invalid_event_modifier_combination('passive', 'preventDefault')); return component.error(
handler,
compiler_errors.invalid_event_modifier_combination('passive', 'preventDefault')
);
} }
if (handler.modifiers.has('passive') && handler.modifiers.has('nonpassive')) { if (handler.modifiers.has('passive') && handler.modifiers.has('nonpassive')) {
return component.error(handler, compiler_errors.invalid_event_modifier_combination('passive', 'nonpassive')); return component.error(
handler,
compiler_errors.invalid_event_modifier_combination('passive', 'nonpassive')
);
} }
handler.modifiers.forEach(modifier => { handler.modifiers.forEach((modifier) => {
if (!valid_modifiers.has(modifier)) { if (!valid_modifiers.has(modifier)) {
return component.error(handler, compiler_errors.invalid_event_modifier(list(Array.from(valid_modifiers)))); return component.error(
handler,
compiler_errors.invalid_event_modifier(list(Array.from(valid_modifiers)))
);
} }
if (modifier === 'passive') { if (modifier === 'passive') {
@ -1229,7 +1384,12 @@ export default class Element extends Node {
} }
}); });
if (passive_events.has(handler.name) && handler.can_make_passive && !handler.modifiers.has('preventDefault') && !handler.modifiers.has('nonpassive')) { if (
passive_events.has(handler.name) &&
handler.can_make_passive &&
!handler.modifiers.has('preventDefault') &&
!handler.modifiers.has('nonpassive')
) {
// touch/wheel events should be passive by default // touch/wheel events should be passive by default
handler.modifiers.add('passive'); handler.modifiers.add('passive');
} }
@ -1241,14 +1401,14 @@ export default class Element extends Node {
} }
add_css_class() { add_css_class() {
if (this.attributes.some(attr => attr.is_spread)) { if (this.attributes.some((attr) => attr.is_spread)) {
this.needs_manual_style_scoping = true; this.needs_manual_style_scoping = true;
return; return;
} }
const { id } = this.component.stylesheet; const { id } = this.component.stylesheet;
const class_attribute = this.attributes.find(a => a.name === 'class'); const class_attribute = this.attributes.find((a) => a.name === 'class');
if (class_attribute && !class_attribute.is_true) { if (class_attribute && !class_attribute.is_true) {
if (class_attribute.chunks.length === 1 && class_attribute.chunks[0].type === 'Text') { if (class_attribute.chunks.length === 1 && class_attribute.chunks[0].type === 'Text') {
@ -1274,12 +1434,14 @@ export default class Element extends Node {
} }
get slot_template_name() { get slot_template_name() {
return this.attributes.find(attribute => attribute.name === 'slot').get_static_value() as string; return this.attributes
.find((attribute) => attribute.name === 'slot')
.get_static_value() as string;
} }
optimise() { optimise() {
attributes_to_compact_whitespace.forEach(attribute_name => { attributes_to_compact_whitespace.forEach((attribute_name) => {
const attribute = this.attributes.find(a => a.name === attribute_name); const attribute = this.attributes.find((a) => a.name === attribute_name);
if (attribute && !attribute.is_true) { if (attribute && !attribute.is_true) {
attribute.chunks.forEach((chunk, index) => { attribute.chunks.forEach((chunk, index) => {
if (chunk.type === 'Text') { if (chunk.type === 'Text') {
@ -1297,13 +1459,21 @@ export default class Element extends Node {
} }
get can_use_textcontent() { get can_use_textcontent() {
return this.is_static_content && this.children.every(node => node.type === 'Text' || node.type === 'MustacheTag'); return (
this.is_static_content &&
this.children.every((node) => node.type === 'Text' || node.type === 'MustacheTag')
);
} }
get can_optimise_to_html_string() { get can_optimise_to_html_string() {
const can_use_textcontent = this.can_use_textcontent; const can_use_textcontent = this.can_use_textcontent;
const is_template_with_text_content = this.name === 'template' && can_use_textcontent; const is_template_with_text_content = this.name === 'template' && can_use_textcontent;
return !is_template_with_text_content && !this.namespace && (this.can_use_innerhtml || can_use_textcontent) && this.children.length > 0; return (
!is_template_with_text_content &&
!this.namespace &&
(this.can_use_innerhtml || can_use_textcontent) &&
this.children.length > 0
);
} }
hash() { hash() {
@ -1313,15 +1483,12 @@ export default class Element extends Node {
const regex_starts_with_vowel = /^[aeiou]/; const regex_starts_with_vowel = /^[aeiou]/;
function should_have_attribute( function should_have_attribute(node, attributes: string[], name = node.name) {
node,
attributes: string[],
name = node.name
) {
const article = regex_starts_with_vowel.test(attributes[0]) ? 'an' : 'a'; const article = regex_starts_with_vowel.test(attributes[0]) ? 'an' : 'a';
const sequence = attributes.length > 1 ? const sequence =
attributes.slice(0, -1).join(', ') + ` or ${attributes[attributes.length - 1]}` : attributes.length > 1
attributes[0]; ? attributes.slice(0, -1).join(', ') + ` or ${attributes[attributes.length - 1]}`
: attributes[0];
node.component.warn(node, compiler_warnings.a11y_missing_attribute(name, article, sequence)); node.component.warn(node, compiler_warnings.a11y_missing_attribute(name, article, sequence));
} }

@ -15,7 +15,7 @@ export default class ElseBlock extends AbstractBlock {
super(component, parent, scope, info); super(component, parent, scope, info);
this.scope = scope.child(); this.scope = scope.child();
([this.const_tags, this.children] = get_const_tags(info.children, component, this, this)); [this.const_tags, this.children] = get_const_tags(info.children, component, this, this);
this.warn_if_empty_block(); this.warn_if_empty_block();
} }

@ -17,7 +17,12 @@ export default class EventHandler extends Node {
uses_context = false; uses_context = false;
can_make_passive = false; can_make_passive = false;
constructor(component: Component, parent: Node, template_scope: TemplateScope, info: TemplateNode) { constructor(
component: Component,
parent: Node,
template_scope: TemplateScope,
info: TemplateNode
) {
super(component, parent, template_scope, info); super(component, parent, template_scope, info);
this.name = info.name; this.name = info.name;
@ -27,7 +32,10 @@ export default class EventHandler extends Node {
this.expression = new Expression(component, this, template_scope, info.expression); this.expression = new Expression(component, this, template_scope, info.expression);
this.uses_context = this.expression.uses_context; this.uses_context = this.expression.uses_context;
if (regex_contains_term_function_expression.test(info.expression.type) && info.expression.params.length === 0) { if (
regex_contains_term_function_expression.test(info.expression.type) &&
info.expression.params.length === 0
) {
// TODO make this detection more accurate — if `event.preventDefault` isn't called, and // TODO make this detection more accurate — if `event.preventDefault` isn't called, and
// `event` is passed to another function, we can make it passive // `event` is passed to another function, we can make it passive
this.can_make_passive = true; this.can_make_passive = true;
@ -37,11 +45,19 @@ export default class EventHandler extends Node {
if (node) { if (node) {
if (node.type === 'VariableDeclaration') { if (node.type === 'VariableDeclaration') {
// for `const handleClick = () => {...}`, we want the [arrow] function expression node // for `const handleClick = () => {...}`, we want the [arrow] function expression node
const declarator = node.declarations.find(d => (d.id as Identifier).name === info.expression.name); const declarator = node.declarations.find(
(d) => (d.id as Identifier).name === info.expression.name
);
node = declarator && declarator.init; node = declarator && declarator.init;
} }
if (node && (node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration' || node.type === 'ArrowFunctionExpression') && node.params.length === 0) { if (
node &&
(node.type === 'FunctionExpression' ||
node.type === 'FunctionDeclaration' ||
node.type === 'ArrowFunctionExpression') &&
node.params.length === 0
) {
this.can_make_passive = true; this.can_make_passive = true;
} }
} }

@ -22,9 +22,14 @@ export default class Head extends Node {
return; return;
} }
this.children = map_children(component, parent, scope, info.children.filter(child => { this.children = map_children(
return (child.type !== 'Text' || regex_non_whitespace_character.test(child.data)); component,
})); parent,
scope,
info.children.filter((child) => {
return child.type !== 'Text' || regex_non_whitespace_character.test(child.data);
})
);
if (this.children.length > 0) { if (this.children.length > 0) {
this.id = `svelte-${hash(this.component.source.slice(this.start, this.end))}`; this.id = `svelte-${hash(this.component.source.slice(this.start, this.end))}`;

@ -22,11 +22,9 @@ export default class IfBlock extends AbstractBlock {
this.not_static_content(); this.not_static_content();
this.expression = new Expression(component, this, this.scope, info.expression); this.expression = new Expression(component, this, this.scope, info.expression);
([this.const_tags, this.children] = get_const_tags(info.children, component, this, this)); [this.const_tags, this.children] = get_const_tags(info.children, component, this, this);
this.else = info.else this.else = info.else ? new ElseBlock(component, this, scope, info.else) : null;
? new ElseBlock(component, this, scope, info.else)
: null;
this.warn_if_empty_block(); this.warn_if_empty_block();
} }

@ -40,11 +40,12 @@ export default class InlineComponent extends Node {
this.name = info.name; this.name = info.name;
this.namespace = get_namespace(parent, component.namespace); this.namespace = get_namespace(parent, component.namespace);
this.expression = this.name === 'svelte:component' this.expression =
this.name === 'svelte:component'
? new Expression(component, this, scope, info.expression) ? new Expression(component, this, scope, info.expression)
: null; : null;
info.attributes.forEach(node => { info.attributes.forEach((node) => {
/* eslint-disable no-fallthrough */ /* eslint-disable no-fallthrough */
switch (node.type) { switch (node.type) {
case 'Action': case 'Action':
@ -90,10 +91,10 @@ export default class InlineComponent extends Node {
if (this.lets.length > 0) { if (this.lets.length > 0) {
this.scope = scope.child(); this.scope = scope.child();
this.lets.forEach(l => { this.lets.forEach((l) => {
const dependencies = new Set([l.name.name]); const dependencies = new Set([l.name.name]);
l.names.forEach(name => { l.names.forEach((name) => {
this.scope.add(name, dependencies, this); this.scope.add(name, dependencies, this);
}); });
}); });
@ -101,8 +102,8 @@ export default class InlineComponent extends Node {
this.scope = scope; this.scope = scope;
} }
this.handlers.forEach(handler => { this.handlers.forEach((handler) => {
handler.modifiers.forEach(modifier => { handler.modifiers.forEach((modifier) => {
if (modifier !== 'once') { if (modifier !== 'once') {
return component.error(handler, compiler_errors.invalid_event_modifier_component); return component.error(handler, compiler_errors.invalid_event_modifier_component);
} }
@ -115,7 +116,10 @@ export default class InlineComponent extends Node {
if (child.type === 'SlotTemplate') { if (child.type === 'SlotTemplate') {
children.push(child); children.push(child);
info.children.splice(i, 1); info.children.splice(i, 1);
} else if ((child.type === 'Element' || child.type === 'InlineComponent' || child.type === 'Slot') && child.attributes.find(attribute => attribute.name === 'slot')) { } else if (
(child.type === 'Element' || child.type === 'InlineComponent' || child.type === 'Slot') &&
child.attributes.find((attribute) => attribute.name === 'slot')
) {
const slot_template = { const slot_template = {
start: child.start, start: child.start,
end: child.end, end: child.end,
@ -151,7 +155,7 @@ export default class InlineComponent extends Node {
} }
} }
if (info.children.some(node => not_whitespace_text(node))) { if (info.children.some((node) => not_whitespace_text(node))) {
children.push({ children.push({
start: info.start, start: info.start,
end: info.end, end: info.end,
@ -166,7 +170,9 @@ export default class InlineComponent extends Node {
} }
get slot_template_name() { get slot_template_name() {
return this.attributes.find(attribute => attribute.name === 'slot').get_static_value() as string; return this.attributes
.find((attribute) => attribute.name === 'slot')
.get_static_value() as string;
} }
} }

@ -17,7 +17,7 @@ export default class Slot extends Element {
constructor(component: Component, parent: INode, scope: TemplateScope, info: TemplateNode) { constructor(component: Component, parent: INode, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info); super(component, parent, scope, info);
info.attributes.forEach(attr => { info.attributes.forEach((attr) => {
if (attr.type !== 'Attribute' && attr.type !== 'Spread') { if (attr.type !== 'Attribute' && attr.type !== 'Spread') {
return component.error(attr, compiler_errors.invalid_slot_directive); return component.error(attr, compiler_errors.invalid_slot_directive);
} }

@ -17,12 +17,7 @@ export default class SlotTemplate extends Node {
slot_attribute: Attribute; slot_attribute: Attribute;
slot_template_name: string = 'default'; slot_template_name: string = 'default';
constructor( constructor(component: Component, parent: INode, scope: TemplateScope, info: any) {
component: Component,
parent: INode,
scope: TemplateScope,
info: any
) {
super(component, parent, scope, info); super(component, parent, scope, info);
this.validate_slot_template_placement(); this.validate_slot_template_placement();
@ -62,7 +57,7 @@ export default class SlotTemplate extends Node {
}); });
this.scope = scope; this.scope = scope;
([this.const_tags, this.children] = get_const_tags(info.children, component, this, this)); [this.const_tags, this.children] = get_const_tags(info.children, component, this, this);
} }
validate_slot_template_placement() { validate_slot_template_placement() {

@ -16,12 +16,7 @@ export default class StyleDirective extends Node {
expression: Expression; expression: Expression;
should_cache: boolean; should_cache: boolean;
constructor( constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
component: Component,
parent: Node,
scope: TemplateScope,
info: TemplateNode
) {
super(component, parent, scope, info); super(component, parent, scope, info);
this.name = info.name; this.name = info.name;
@ -31,9 +26,7 @@ export default class StyleDirective extends Node {
if (!valid_modifiers.has(modifier)) { if (!valid_modifiers.has(modifier)) {
component.error( component.error(
this, this,
compiler_errors.invalid_style_directive_modifier( compiler_errors.invalid_style_directive_modifier(list([...valid_modifiers]))
list([...valid_modifiers])
)
); );
} }
} }
@ -41,13 +34,14 @@ export default class StyleDirective extends Node {
// Convert the value array to an expression so it's easier to handle // Convert the value array to an expression so it's easier to handle
// the StyleDirective going forward. // the StyleDirective going forward.
if (info.value === true || (info.value.length === 1 && info.value[0].type === 'MustacheTag')) { if (info.value === true || (info.value.length === 1 && info.value[0].type === 'MustacheTag')) {
const identifier = info.value === true const identifier =
? { info.value === true
? ({
type: 'Identifier', type: 'Identifier',
start: info.end - info.name.length, start: info.end - info.name.length,
end: info.end, end: info.end,
name: info.name name: info.name
} as any } as any)
: info.value[0].expression; : info.value[0].expression;
this.expression = new Expression(component, this, scope, identifier); this.expression = new Expression(component, this, scope, identifier);
this.should_cache = false; this.should_cache = false;

@ -8,14 +8,7 @@ import { regex_non_whitespace_character } from '../../utils/patterns';
// Whitespace inside one of these elements will not result in // Whitespace inside one of these elements will not result in
// a whitespace node being created in any circumstances. (This // a whitespace node being created in any circumstances. (This
// list is almost certainly very incomplete) // list is almost certainly very incomplete)
const elements_without_text = new Set([ const elements_without_text = new Set(['audio', 'datalist', 'dl', 'optgroup', 'select', 'video']);
'audio',
'datalist',
'dl',
'optgroup',
'select',
'video'
]);
const regex_ends_with_svg = /svg$/; const regex_ends_with_svg = /svg$/;
const regex_non_whitespace_characters = /[\S\u00A0]/; const regex_non_whitespace_characters = /[\S\u00A0]/;
@ -38,7 +31,8 @@ export default class Text extends Node {
if (!parent_element) return false; if (!parent_element) return false;
if (parent_element.type === 'Head') return true; if (parent_element.type === 'Head') return true;
if (parent_element.type === 'InlineComponent') return parent_element.children.length === 1 && this === parent_element.children[0]; if (parent_element.type === 'InlineComponent')
return parent_element.children.length === 1 && this === parent_element.children[0];
// svg namespace exclusions // svg namespace exclusions
if (regex_ends_with_svg.test(parent_element.namespace)) { if (regex_ends_with_svg.test(parent_element.namespace)) {

@ -16,13 +16,13 @@ export default class ThenBlock extends AbstractBlock {
this.scope = scope.child(); this.scope = scope.child();
if (parent.then_node) { if (parent.then_node) {
parent.then_contexts.forEach(context => { parent.then_contexts.forEach((context) => {
if (context.type !== 'DestructuredVariable') return; if (context.type !== 'DestructuredVariable') return;
this.scope.add(context.key.name, parent.expression.dependencies, this); this.scope.add(context.key.name, parent.expression.dependencies, this);
}); });
} }
([this.const_tags, this.children] = get_const_tags(info.children, component, this, parent)); [this.const_tags, this.children] = get_const_tags(info.children, component, this, parent);
if (!info.skip) { if (!info.skip) {
this.warn_if_empty_block(); this.warn_if_empty_block();

@ -19,17 +19,15 @@ export default class Title extends Node {
return; return;
} }
info.children.forEach(child => { info.children.forEach((child) => {
if (child.type !== 'Text' && child.type !== 'MustacheTag') { if (child.type !== 'Text' && child.type !== 'MustacheTag') {
return component.error(child, compiler_errors.illegal_structure_title); return component.error(child, compiler_errors.illegal_structure_title);
} }
}); });
this.should_cache = info.children.length === 1 this.should_cache =
? ( info.children.length === 1
info.children[0].type !== 'Identifier' || ? info.children[0].type !== 'Identifier' || scope.names.has(info.children[0].name)
scope.names.has(info.children[0].name)
)
: true; : true;
} }
} }

@ -25,8 +25,11 @@ export default class Transition extends Node {
this.is_local = info.modifiers.includes('local'); this.is_local = info.modifiers.includes('local');
if ((info.intro && parent.intro) || (info.outro && parent.outro)) { if ((info.intro && parent.intro) || (info.outro && parent.outro)) {
const parent_transition = (parent.intro || parent.outro); const parent_transition = parent.intro || parent.outro;
component.error(info, compiler_errors.duplicate_transition(this.directive, parent_transition.directive)); component.error(
info,
compiler_errors.duplicate_transition(this.directive, parent_transition.directive)
);
return; return;
} }

@ -30,7 +30,7 @@ export default class Window extends Node {
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) { constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info); super(component, parent, scope, info);
info.attributes.forEach(node => { info.attributes.forEach((node) => {
if (node.type === 'EventHandler') { if (node.type === 'EventHandler') {
this.handlers.push(new EventHandler(component, this, scope, node)); this.handlers.push(new EventHandler(component, this, scope, node));
} else if (node.type === 'Binding') { } else if (node.type === 'Binding') {
@ -42,16 +42,31 @@ export default class Window extends Node {
} }
if (!~valid_bindings.indexOf(node.name)) { if (!~valid_bindings.indexOf(node.name)) {
const match = ( const match =
node.name === 'width' ? 'innerWidth' : node.name === 'width'
node.name === 'height' ? 'innerHeight' : ? 'innerWidth'
fuzzymatch(node.name, valid_bindings) : node.name === 'height'
); ? 'innerHeight'
: fuzzymatch(node.name, valid_bindings);
if (match) { if (match) {
return component.error(node, compiler_errors.invalid_binding_on(node.name, '<svelte:window>', ` (did you mean '${match}'?)`)); return component.error(
node,
compiler_errors.invalid_binding_on(
node.name,
'<svelte:window>',
` (did you mean '${match}'?)`
)
);
} else { } else {
return component.error(node, compiler_errors.invalid_binding_on(node.name, '<svelte:window>', ` — valid bindings are ${list(valid_bindings)}`)); return component.error(
node,
compiler_errors.invalid_binding_on(
node.name,
'<svelte:window>',
` — valid bindings are ${list(valid_bindings)}`
)
);
} }
} }

@ -37,7 +37,8 @@ import Window from './Window';
// note: to write less types each of types in union below should have type defined as literal // note: to write less types each of types in union below should have type defined as literal
// https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#discriminating-unions // https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#discriminating-unions
export type INode = Action export type INode =
| Action
| Animation | Animation
| Attribute | Attribute
| AwaitBlock | AwaitBlock

@ -16,7 +16,7 @@ interface ComputedProperty {
} }
interface DestructuredVariable { interface DestructuredVariable {
type: 'DestructuredVariable' type: 'DestructuredVariable';
key: Identifier; key: Identifier;
name?: string; name?: string;
modifier: (node: Node) => Node; modifier: (node: Node) => Node;
@ -112,9 +112,7 @@ export function unpack_destructuring({
contexts, contexts,
node: property.argument, node: property.argument,
modifier: (node) => modifier: (node) =>
x`@object_without_properties(${modifier( x`@object_without_properties(${modifier(node)}, [${used_properties}])` as Node,
node
)}, [${used_properties}])` as Node,
default_modifier, default_modifier,
scope, scope,
component, component,
@ -217,12 +215,7 @@ function update_reference(
expression = clone(expression) as Expression; expression = clone(expression) as Expression;
walk(expression, { walk(expression, {
enter(node, parent: Node) { enter(node, parent: Node) {
if ( if (is_reference(node as NodeWithPropertyDefinition, parent as NodeWithPropertyDefinition)) {
is_reference(
node as NodeWithPropertyDefinition,
parent as NodeWithPropertyDefinition
)
) {
this.replace(find_from_context(node as Identifier)); this.replace(find_from_context(node as Identifier));
this.skip(); this.skip();
} }
@ -232,11 +225,7 @@ function update_reference(
return expression; return expression;
} }
function mark_referenced( function mark_referenced(node: Node, scope: TemplateScope, component: Component) {
node: Node,
scope: TemplateScope,
component: Component
) {
walk(node, { walk(node, {
enter(node: any, parent: any) { enter(node: any, parent: any) {
if (is_reference(node, parent)) { if (is_reference(node, parent)) {

@ -36,12 +36,18 @@ export default class Expression {
scope: Scope; scope: Scope;
scope_map: WeakMap<Node, Scope>; scope_map: WeakMap<Node, Scope>;
declarations: Array<(Node | Node[])> = []; declarations: Array<Node | Node[]> = [];
uses_context = false; uses_context = false;
manipulated: Node; manipulated: Node;
constructor(component: Component, owner: Owner, template_scope: TemplateScope, info: Node, lazy?: boolean) { constructor(
component: Component,
owner: Owner,
template_scope: TemplateScope,
info: Node,
lazy?: boolean
) {
// TODO revert to direct property access in prod? // TODO revert to direct property access in prod?
Object.defineProperties(this, { Object.defineProperties(this, {
component: { component: {
@ -105,7 +111,9 @@ export default class Expression {
const is_index = owner.type === 'EachBlock' && owner.key && name === owner.index; const is_index = owner.type === 'EachBlock' && owner.key && name === owner.index;
if (!lazy || is_index) { if (!lazy || is_index) {
template_scope.dependencies_for_name.get(name).forEach(name => dependencies.add(name)); template_scope.dependencies_for_name
.get(name)
.forEach((name) => dependencies.add(name));
} }
} else { } else {
if (!lazy) { if (!lazy) {
@ -134,13 +142,13 @@ export default class Expression {
} }
if (names) { if (names) {
names.forEach(name => { names.forEach((name) => {
if (template_scope.names.has(name)) { if (template_scope.names.has(name)) {
if (template_scope.is_const(name)) { if (template_scope.is_const(name)) {
component.error(node, compiler_errors.invalid_const_update(name)); component.error(node, compiler_errors.invalid_const_update(name));
} }
template_scope.dependencies_for_name.get(name).forEach(name => { template_scope.dependencies_for_name.get(name).forEach((name) => {
const variable = component.var_lookup.get(name); const variable = component.var_lookup.get(name);
if (variable) variable[deep ? 'mutated' : 'reassigned'] = true; if (variable) variable[deep ? 'mutated' : 'reassigned'] = true;
}); });
@ -188,7 +196,7 @@ export default class Expression {
} }
dynamic_dependencies() { dynamic_dependencies() {
return Array.from(this.dependencies).filter(name => { return Array.from(this.dependencies).filter((name) => {
if (this.template_scope.is_let(name)) return true; if (this.template_scope.is_let(name)) return true;
if (is_reserved_keyword(name)) return true; if (is_reserved_keyword(name)) return true;
@ -198,11 +206,13 @@ export default class Expression {
} }
dynamic_contextual_dependencies() { dynamic_contextual_dependencies() {
return Array.from(this.contextual_dependencies).filter(name => { return Array.from(this.contextual_dependencies).filter((name) => {
return Array.from(this.template_scope.dependencies_for_name.get(name)).some(variable_name => { return Array.from(this.template_scope.dependencies_for_name.get(name)).some(
(variable_name) => {
const variable = this.component.var_lookup.get(variable_name); const variable = this.component.var_lookup.get(variable_name);
return is_dynamic(variable); return is_dynamic(variable);
}); }
);
}); });
} }
@ -212,13 +222,7 @@ export default class Expression {
// multiple times // multiple times
if (this.manipulated) return this.manipulated; if (this.manipulated) return this.manipulated;
const { const { component, declarations, scope_map: map, template_scope, owner } = this;
component,
declarations,
scope_map: map,
template_scope,
owner
} = this;
let scope = this.scope; let scope = this.scope;
let function_expression; let function_expression;
@ -246,7 +250,7 @@ export default class Expression {
if (template_scope.names.has(name)) { if (template_scope.names.has(name)) {
contextual_dependencies.add(name); contextual_dependencies.add(name);
template_scope.dependencies_for_name.get(name).forEach(dependency => { template_scope.dependencies_for_name.get(name).forEach((dependency) => {
dependencies.add(dependency); dependencies.add(dependency);
}); });
} else { } else {
@ -278,9 +282,7 @@ export default class Expression {
if (map.has(node)) scope = scope.parent; if (map.has(node)) scope = scope.parent;
if (node === function_expression) { if (node === function_expression) {
const id = component.get_unique_name( const id = component.get_unique_name(sanitize(get_function_name(node, owner)));
sanitize(get_function_name(node, owner))
);
const declaration = b`const ${id} = ${node}`; const declaration = b`const ${id} = ${node}`;
const extract_functions = () => { const extract_functions = () => {
@ -289,11 +291,11 @@ export default class Expression {
const has_args = function_expression.params.length > 0; const has_args = function_expression.params.length > 0;
function_expression.params = [ function_expression.params = [
...deps.map(name => ({ type: 'Identifier', name } as Identifier)), ...deps.map((name) => ({ type: 'Identifier', name } as Identifier)),
...function_expression.params ...function_expression.params
]; ];
const context_args = deps.map(name => block.renderer.reference(name, ctx)); const context_args = deps.map((name) => block.renderer.reference(name, ctx));
component.partly_hoisted.push(declaration); component.partly_hoisted.push(declaration);
@ -355,7 +357,7 @@ export default class Expression {
const { deps, func_declaration } = extract_functions(); const { deps, func_declaration } = extract_functions();
if (owner.type === 'Attribute' && owner.parent.name === 'slot') { if (owner.type === 'Attribute' && owner.parent.name === 'slot') {
const dep_scopes = new Set<INode>(deps.map(name => template_scope.get_owner(name))); const dep_scopes = new Set<INode>(deps.map((name) => template_scope.get_owner(name)));
// find the nearest scopes // find the nearest scopes
let node: INode = owner.parent; let node: INode = owner.parent;
while (node && !dep_scopes.has(node)) { while (node && !dep_scopes.has(node)) {
@ -385,7 +387,7 @@ export default class Expression {
type: 'DestructuredVariable', type: 'DestructuredVariable',
key: func_id, key: func_id,
modifier: () => func_expression, modifier: () => func_expression,
default_modifier: node => node default_modifier: (node) => node
}); });
this.replace(block.renderer.reference(func_id)); this.replace(block.renderer.reference(func_id));
} }
@ -417,10 +419,10 @@ export default class Expression {
const names = new Set(extract_names(assignee as Node)); const names = new Set(extract_names(assignee as Node));
const traced: Set<string> = new Set(); const traced: Set<string> = new Set();
names.forEach(name => { names.forEach((name) => {
const dependencies = template_scope.dependencies_for_name.get(name); const dependencies = template_scope.dependencies_for_name.get(name);
if (dependencies) { if (dependencies) {
dependencies.forEach(name => traced.add(name)); dependencies.forEach((name) => traced.add(name));
} else { } else {
traced.add(name); traced.add(name);
} }
@ -450,7 +452,7 @@ export default class Expression {
if (declarations.length > 0) { if (declarations.length > 0) {
block.maintain_context = true; block.maintain_context = true;
declarations.forEach(declaration => { declarations.forEach((declaration) => {
block.chunks.init.push(declaration); block.chunks.init.push(declaration);
}); });
} }

@ -57,7 +57,9 @@ export default class Node {
} }
get_static_attribute_value(name: string) { get_static_attribute_value(name: string) {
const attribute = this.attributes && this.attributes.find( const attribute =
this.attributes &&
this.attributes.find(
(attr: Attribute) => attr.type === 'Attribute' && attr.name.toLowerCase() === name (attr: Attribute) => attr.type === 'Attribute' && attr.name.toLowerCase() === name
); );
@ -74,8 +76,6 @@ export default class Node {
} }
has_ancestor(type: string) { has_ancestor(type: string) {
return this.parent ? return this.parent ? this.parent.type === type || this.parent.has_ancestor(type) : false;
this.parent.type === type || this.parent.has_ancestor(type) :
false;
} }
} }

@ -13,13 +13,15 @@ export default class Tag extends Node {
this.expression = new Expression(component, this, scope, info.expression); this.expression = new Expression(component, this, scope, info.expression);
this.should_cache = ( this.should_cache =
info.expression.type !== 'Identifier' || info.expression.type !== 'Identifier' ||
(this.expression.dependencies.size && scope.names.has(info.expression.name)) (this.expression.dependencies.size && scope.names.has(info.expression.name));
);
} }
is_dependencies_static() { is_dependencies_static() {
return this.expression.dynamic_contextual_dependencies().length === 0 && this.expression.dynamic_dependencies().length === 0; return (
this.expression.dynamic_contextual_dependencies().length === 0 &&
this.expression.dynamic_dependencies().length === 0
);
} }
check_if_content_dynamic() { check_if_content_dynamic() {
if (!this.is_dependencies_static()) { if (!this.is_dependencies_static()) {

@ -6,7 +6,14 @@ import Element from '../Element';
import SlotTemplate from '../SlotTemplate'; import SlotTemplate from '../SlotTemplate';
import ConstTag from '../ConstTag'; import ConstTag from '../ConstTag';
type NodeWithScope = EachBlock | ThenBlock | CatchBlock | InlineComponent | Element | SlotTemplate | ConstTag; type NodeWithScope =
| EachBlock
| ThenBlock
| CatchBlock
| InlineComponent
| Element
| SlotTemplate
| ConstTag;
export default class TemplateScope { export default class TemplateScope {
names: Set<string>; names: Set<string>;
@ -33,7 +40,7 @@ export default class TemplateScope {
} }
is_top_level(name: string) { is_top_level(name: string) {
return !this.parent || !this.names.has(name) && this.parent.is_top_level(name); return !this.parent || (!this.names.has(name) && this.parent.is_top_level(name));
} }
get_owner(name: string): NodeWithScope { get_owner(name: string): NodeWithScope {
@ -42,7 +49,12 @@ export default class TemplateScope {
is_let(name: string) { is_let(name: string) {
const owner = this.get_owner(name); const owner = this.get_owner(name);
return owner && (owner.type === 'Element' || owner.type === 'InlineComponent' || owner.type === 'SlotTemplate'); return (
owner &&
(owner.type === 'Element' ||
owner.type === 'InlineComponent' ||
owner.type === 'SlotTemplate')
);
} }
is_await(name: string) { is_await(name: string) {

@ -6,7 +6,12 @@ import { INodeAllowConstTag, INode } from '../interfaces';
import check_graph_for_cycles from '../../utils/check_graph_for_cycles'; import check_graph_for_cycles from '../../utils/check_graph_for_cycles';
import compiler_errors from '../../compiler_errors'; import compiler_errors from '../../compiler_errors';
export default function get_const_tags(children: TemplateNode[], component: Component, node: INodeAllowConstTag, parent: INode): [ConstTag[], Array<Exclude<INode, ConstTag>>] { export default function get_const_tags(
children: TemplateNode[],
component: Component,
node: INodeAllowConstTag,
parent: INode
): [ConstTag[], Array<Exclude<INode, ConstTag>>] {
const const_tags: ConstTagType[] = []; const const_tags: ConstTagType[] = [];
const others: Array<Exclude<TemplateNode, ConstTagType>> = []; const others: Array<Exclude<TemplateNode, ConstTagType>> = [];
@ -18,9 +23,9 @@ export default function get_const_tags(children: TemplateNode[], component: Comp
} }
} }
const consts_nodes = const_tags.map(tag => new ConstTag(component, node, node.scope, tag)); const consts_nodes = const_tags.map((tag) => new ConstTag(component, node, node.scope, tag));
const sorted_consts_nodes = sort_consts_nodes(consts_nodes, component); const sorted_consts_nodes = sort_consts_nodes(consts_nodes, component);
sorted_consts_nodes.forEach(node => node.parse_expression()); sorted_consts_nodes.forEach((node) => node.parse_expression());
const children_nodes = map_children(component, parent, node.scope, others); const children_nodes = map_children(component, parent, node.scope, others);
@ -35,7 +40,7 @@ function sort_consts_nodes(consts_nodes: ConstTag[], component: Component) {
}; };
const sorted_consts_nodes: ConstNode[] = []; const sorted_consts_nodes: ConstNode[] = [];
const unsorted_consts_nodes: ConstNode[] = consts_nodes.map(node => { const unsorted_consts_nodes: ConstNode[] = consts_nodes.map((node) => {
return { return {
assignees: node.assignees, assignees: node.assignees,
dependencies: node.dependencies, dependencies: node.dependencies,
@ -45,8 +50,8 @@ function sort_consts_nodes(consts_nodes: ConstTag[], component: Component) {
const lookup = new Map(); const lookup = new Map();
unsorted_consts_nodes.forEach(node => { unsorted_consts_nodes.forEach((node) => {
node.assignees.forEach(name => { node.assignees.forEach((name) => {
if (!lookup.has(name)) { if (!lookup.has(name)) {
lookup.set(name, []); lookup.set(name, []);
} }
@ -54,16 +59,18 @@ function sort_consts_nodes(consts_nodes: ConstTag[], component: Component) {
}); });
}); });
const cycle = check_graph_for_cycles(unsorted_consts_nodes.reduce((acc, node) => { const cycle = check_graph_for_cycles(
node.assignees.forEach(v => { unsorted_consts_nodes.reduce((acc, node) => {
node.dependencies.forEach(w => { node.assignees.forEach((v) => {
node.dependencies.forEach((w) => {
if (!node.assignees.has(w)) { if (!node.assignees.has(w)) {
acc.push([v, w]); acc.push([v, w]);
} }
}); });
}); });
return acc; return acc;
}, [])); }, [])
);
if (cycle && cycle.length) { if (cycle && cycle.length) {
const nodeList = lookup.get(cycle[0]); const nodeList = lookup.get(cycle[0]);
@ -74,7 +81,7 @@ function sort_consts_nodes(consts_nodes: ConstTag[], component: Component) {
const add_node = (node: ConstNode) => { const add_node = (node: ConstNode) => {
if (sorted_consts_nodes.includes(node)) return; if (sorted_consts_nodes.includes(node)) return;
node.dependencies.forEach(name => { node.dependencies.forEach((name) => {
if (node.assignees.has(name)) return; if (node.assignees.has(name)) return;
const earlier_nodes = lookup.get(name); const earlier_nodes = lookup.get(name);
if (earlier_nodes) { if (earlier_nodes) {
@ -87,5 +94,5 @@ function sort_consts_nodes(consts_nodes: ConstTag[], component: Component) {
unsorted_consts_nodes.forEach(add_node); unsorted_consts_nodes.forEach(add_node);
return sorted_consts_nodes.map(node => node.node); return sorted_consts_nodes.map((node) => node.node);
} }

@ -25,27 +25,48 @@ export type Children = ReturnType<typeof map_children>;
function get_constructor(type) { function get_constructor(type) {
switch (type) { switch (type) {
case 'AwaitBlock': return AwaitBlock; case 'AwaitBlock':
case 'Body': return Body; return AwaitBlock;
case 'Comment': return Comment; case 'Body':
case 'ConstTag': return ConstTag; return Body;
case 'Document': return Document; case 'Comment':
case 'EachBlock': return EachBlock; return Comment;
case 'Element': return Element; case 'ConstTag':
case 'Head': return Head; return ConstTag;
case 'IfBlock': return IfBlock; case 'Document':
case 'InlineComponent': return InlineComponent; return Document;
case 'KeyBlock': return KeyBlock; case 'EachBlock':
case 'MustacheTag': return MustacheTag; return EachBlock;
case 'Options': return Options; case 'Element':
case 'RawMustacheTag': return RawMustacheTag; return Element;
case 'DebugTag': return DebugTag; case 'Head':
case 'Slot': return Slot; return Head;
case 'SlotTemplate': return SlotTemplate; case 'IfBlock':
case 'Text': return Text; return IfBlock;
case 'Title': return Title; case 'InlineComponent':
case 'Window': return Window; return InlineComponent;
default: throw new Error(`Not implemented: ${type}`); case 'KeyBlock':
return KeyBlock;
case 'MustacheTag':
return MustacheTag;
case 'Options':
return Options;
case 'RawMustacheTag':
return RawMustacheTag;
case 'DebugTag':
return DebugTag;
case 'Slot':
return Slot;
case 'SlotTemplate':
return SlotTemplate;
case 'Text':
return Text;
case 'Title':
return Title;
case 'Window':
return Window;
default:
throw new Error(`Not implemented: ${type}`);
} }
} }
@ -53,14 +74,14 @@ export default function map_children(component, parent, scope, children: Templat
let last = null; let last = null;
let ignores = []; let ignores = [];
return children.map(child => { return children.map((child) => {
const constructor = get_constructor(child.type); const constructor = get_constructor(child.type);
const use_ignores = child.type !== 'Text' && child.type !== 'Comment' && ignores.length; const use_ignores = child.type !== 'Text' && child.type !== 'Comment' && ignores.length;
if (use_ignores) component.push_ignores(ignores); if (use_ignores) component.push_ignores(ignores);
const node = new constructor(component, parent, scope, child); const node = new constructor(component, parent, scope, child);
if (use_ignores) component.pop_ignores(), ignores = []; if (use_ignores) component.pop_ignores(), (ignores = []);
if (node.type === 'Comment' && node.ignores.length) { if (node.type === 'Comment' && node.ignores.length) {
push_array(ignores, node.ignores); push_array(ignores, node.ignores);

@ -74,7 +74,7 @@ export default class Block {
get_unique_name: (name: string) => Identifier; get_unique_name: (name: string) => Identifier;
has_update_method = false; has_update_method = false;
autofocus?: { element_var: string, condition_expression?: any }; autofocus?: { element_var: string; condition_expression?: any };
constructor(options: BlockOptions) { constructor(options: BlockOptions) {
this.parent = options.parent; this.parent = options.parent;
@ -156,7 +156,7 @@ export default class Block {
} }
add_dependencies(dependencies: Set<string>) { add_dependencies(dependencies: Set<string>) {
dependencies.forEach(dependency => { dependencies.forEach((dependency) => {
this.dependencies.add(dependency); this.dependencies.add(dependency);
}); });
@ -206,9 +206,7 @@ export default class Block {
add_variable(id: Identifier, init?: Node) { add_variable(id: Identifier, init?: Node) {
if (this.variables.has(id.name)) { if (this.variables.has(id.name)) {
throw new Error( throw new Error(`Variable '${id.name}' already initialised with a different value`);
`Variable '${id.name}' already initialised with a different value`
);
} }
this.variables.set(id.name, { id, init }); this.variables.set(id.name, { id, init });
@ -244,7 +242,9 @@ export default class Block {
if (this.autofocus) { if (this.autofocus) {
if (this.autofocus.condition_expression) { if (this.autofocus.condition_expression) {
this.chunks.mount.push(b`if (${this.autofocus.condition_expression}) ${this.autofocus.element_var}.focus();`); this.chunks.mount.push(
b`if (${this.autofocus.condition_expression}) ${this.autofocus.element_var}.focus();`
);
} else { } else {
this.chunks.mount.push(b`${this.autofocus.element_var}.focus();`); this.chunks.mount.push(b`${this.autofocus.element_var}.focus();`);
} }
@ -267,11 +267,9 @@ export default class Block {
if (this.chunks.create.length === 0 && this.chunks.hydrate.length === 0) { if (this.chunks.create.length === 0 && this.chunks.hydrate.length === 0) {
properties.create = noop; properties.create = noop;
} else { } else {
const hydrate = this.chunks.hydrate.length > 0 && ( const hydrate =
this.renderer.options.hydratable this.chunks.hydrate.length > 0 &&
? b`this.h();` (this.renderer.options.hydratable ? b`this.h();` : this.chunks.hydrate);
: this.chunks.hydrate
);
properties.create = x`function #create() { properties.create = x`function #create() {
${this.chunks.create} ${this.chunks.create}
@ -404,14 +402,13 @@ export default class Block {
${this.chunks.declarations} ${this.chunks.declarations}
${Array.from(this.variables.values()).map(({ id, init }) => { ${Array.from(this.variables.values()).map(({ id, init }) => {
return init return init ? b`let ${id} = ${init}` : b`let ${id}`;
? b`let ${id} = ${init}`
: b`let ${id}`;
})} })}
${this.chunks.init} ${this.chunks.init}
${dev ${
dev
? b` ? b`
const ${block} = ${return_value}; const ${block} = ${return_value};
@dispatch_dev("SvelteRegisterBlock", { @dispatch_dev("SvelteRegisterBlock", {
@ -431,7 +428,8 @@ export default class Block {
} }
has_content(): boolean { has_content(): boolean {
return !!this.first || return (
!!this.first ||
this.event_listeners.length > 0 || this.event_listeners.length > 0 ||
this.chunks.intro.length > 0 || this.chunks.intro.length > 0 ||
this.chunks.outro.length > 0 || this.chunks.outro.length > 0 ||
@ -441,7 +439,8 @@ export default class Block {
this.chunks.mount.length > 0 || this.chunks.mount.length > 0 ||
this.chunks.update.length > 0 || this.chunks.update.length > 0 ||
this.chunks.destroy.length > 0 || this.chunks.destroy.length > 0 ||
this.has_animation; this.has_animation
);
} }
render() { render() {
@ -483,9 +482,7 @@ export default class Block {
` `
); );
this.chunks.destroy.push( this.chunks.destroy.push(b`${dispose}();`);
b`${dispose}();`
);
} else { } else {
this.chunks.mount.push(b` this.chunks.mount.push(b`
if (!#mounted) { if (!#mounted) {
@ -496,9 +493,7 @@ export default class Block {
} }
`); `);
this.chunks.destroy.push( this.chunks.destroy.push(b`@run_all(${dispose});`);
b`@run_all(${dispose});`
);
} }
} }
} }

@ -3,7 +3,16 @@ import { CompileOptions, Var } from '../../interfaces';
import Component from '../Component'; import Component from '../Component';
import FragmentWrapper from './wrappers/Fragment'; import FragmentWrapper from './wrappers/Fragment';
import { x } from 'code-red'; import { x } from 'code-red';
import { Node, Identifier, MemberExpression, Literal, Expression, BinaryExpression, UnaryExpression, ArrayExpression } from 'estree'; import {
Node,
Identifier,
MemberExpression,
Literal,
Expression,
BinaryExpression,
UnaryExpression,
ArrayExpression
} from 'estree';
import flatten_reference from '../utils/flatten_reference'; import flatten_reference from '../utils/flatten_reference';
import { reserved_keywords } from '../utils/reserved_keywords'; import { reserved_keywords } from '../utils/reserved_keywords';
import { renderer_invalidate } from './invalidate'; import { renderer_invalidate } from './invalidate';
@ -57,12 +66,14 @@ export default class Renderer {
this.file_var = options.dev && this.component.get_unique_name('file'); this.file_var = options.dev && this.component.get_unique_name('file');
component.vars.filter(v => !v.hoistable || (v.export_name && !v.module)).forEach(v => this.add_to_context(v.name)); component.vars
.filter((v) => !v.hoistable || (v.export_name && !v.module))
.forEach((v) => this.add_to_context(v.name));
// ensure store values are included in context // ensure store values are included in context
component.vars.filter(v => v.subscribable).forEach(v => this.add_to_context(`$${v.name}`)); component.vars.filter((v) => v.subscribable).forEach((v) => this.add_to_context(`$${v.name}`));
reserved_keywords.forEach(keyword => { reserved_keywords.forEach((keyword) => {
if (component.var_lookup.has(keyword)) { if (component.var_lookup.has(keyword)) {
this.add_to_context(keyword); this.add_to_context(keyword);
} }
@ -97,7 +108,7 @@ export default class Renderer {
); );
// TODO messy // TODO messy
this.blocks.forEach(block => { this.blocks.forEach((block) => {
if (block instanceof Block) { if (block instanceof Block) {
block.assign_variable_names(); block.assign_variable_names();
} }
@ -109,7 +120,7 @@ export default class Renderer {
this.context_overflow = this.context.length > 31; this.context_overflow = this.context.length > 31;
this.context.forEach(member => { this.context.forEach((member) => {
const { variable } = member; const { variable } = member;
if (variable) { if (variable) {
member.priority += 2; member.priority += 2;
@ -117,7 +128,8 @@ export default class Renderer {
// these determine whether variable is included in initial context // these determine whether variable is included in initial context
// array, so must have the highest priority // array, so must have the highest priority
if (variable.is_reactive_dependency && (variable.mutated || variable.reassigned)) member.priority += 16; if (variable.is_reactive_dependency && (variable.mutated || variable.reassigned))
member.priority += 16;
if (variable.export_name) member.priority += 32; if (variable.export_name) member.priority += 32;
if (variable.referenced) member.priority += 64; if (variable.referenced) member.priority += 64;
} else if (member.is_non_contextual) { } else if (member.is_non_contextual) {
@ -131,14 +143,22 @@ export default class Renderer {
} }
}); });
this.context.sort((a, b) => (b.priority - a.priority) || ((a.index.value as number) - (b.index.value as number))); this.context.sort(
this.context.forEach((member, i) => member.index.value = i); (a, b) => b.priority - a.priority || (a.index.value as number) - (b.index.value as number)
);
this.context.forEach((member, i) => (member.index.value = i));
let i = this.context.length; let i = this.context.length;
while (i--) { while (i--) {
const member = this.context[i]; const member = this.context[i];
if (member.variable) { if (member.variable) {
if (member.variable.referenced || member.variable.export_name || (member.variable.is_reactive_dependency && (member.variable.mutated || member.variable.reassigned))) break; if (
member.variable.referenced ||
member.variable.export_name ||
(member.variable.is_reactive_dependency &&
(member.variable.mutated || member.variable.reassigned))
)
break;
} else if (member.is_non_contextual) { } else if (member.is_non_contextual) {
break; break;
} }
@ -180,9 +200,9 @@ export default class Renderer {
dirty(names: string[], is_reactive_declaration = false): Expression { dirty(names: string[], is_reactive_declaration = false): Expression {
const renderer = this; const renderer = this;
const dirty = (is_reactive_declaration const dirty = (is_reactive_declaration ? x`$$self.$$.dirty` : x`#dirty`) as
? x`$$self.$$.dirty` | Identifier
: x`#dirty`) as Identifier | MemberExpression; | MemberExpression;
const get_bitmask = () => { const get_bitmask = () => {
const bitmask: BitMasks = []; const bitmask: BitMasks = [];
@ -197,7 +217,7 @@ export default class Renderer {
const value = member.index.value as number; const value = member.index.value as number;
const i = (value / 31) | 0; const i = (value / 31) | 0;
const n = 1 << (value % 31); const n = 1 << value % 31;
if (!bitmask[i]) bitmask[i] = { n: 0, names: [] }; if (!bitmask[i]) bitmask[i] = { n: 0, names: [] };

@ -7,7 +7,14 @@ import { walk } from 'estree-walker';
import { extract_names, Scope } from 'periscopic'; import { extract_names, Scope } from 'periscopic';
import { invalidate } from './invalidate'; import { invalidate } from './invalidate';
import Block from './Block'; import Block from './Block';
import { ImportDeclaration, ClassDeclaration, Node, Statement, ObjectExpression, Expression } from 'estree'; import {
ImportDeclaration,
ClassDeclaration,
Node,
Statement,
ObjectExpression,
Expression
} from 'estree';
import { apply_preprocessor_sourcemap } from '../../utils/mapped_code'; import { apply_preprocessor_sourcemap } from '../../utils/mapped_code';
import { flatten } from '../../utils/flatten'; import { flatten } from '../../utils/flatten';
import check_enable_sourcemap from '../utils/check_enable_sourcemap'; import check_enable_sourcemap from '../utils/check_enable_sourcemap';
@ -36,21 +43,23 @@ export default function dom(
const css_sourcemap_enabled = check_enable_sourcemap(options.enableSourcemap, 'css'); const css_sourcemap_enabled = check_enable_sourcemap(options.enableSourcemap, 'css');
if (css_sourcemap_enabled) { if (css_sourcemap_enabled) {
css.map = apply_preprocessor_sourcemap(options.filename, css.map, options.sourcemap as string | RawSourceMap | DecodedSourceMap); css.map = apply_preprocessor_sourcemap(
options.filename,
css.map,
options.sourcemap as string | RawSourceMap | DecodedSourceMap
);
} else { } else {
css.map = null; css.map = null;
} }
const styles = css_sourcemap_enabled && component.stylesheet.has_styles && options.dev const styles =
css_sourcemap_enabled && component.stylesheet.has_styles && options.dev
? `${css.code}\n/*# sourceMappingURL=${css.map.toUrl()} */` ? `${css.code}\n/*# sourceMappingURL=${css.map.toUrl()} */`
: css.code; : css.code;
const add_css = component.get_unique_name('add_css'); const add_css = component.get_unique_name('add_css');
const should_add_css = ( const should_add_css = !!styles && (options.customElement || options.css === 'injected');
!!styles &&
(options.customElement || options.css === 'injected')
);
if (should_add_css) { if (should_add_css) {
body.push(b` body.push(b`
@ -64,12 +73,15 @@ export default function dom(
// TODO the deconflicted names of blocks are reversed... should set them here // TODO the deconflicted names of blocks are reversed... should set them here
const blocks = renderer.blocks.slice().reverse(); const blocks = renderer.blocks.slice().reverse();
push_array(body, blocks.map(block => { push_array(
body,
blocks.map((block) => {
// TODO this is a horrible mess — renderer.blocks // TODO this is a horrible mess — renderer.blocks
// contains a mixture of Blocks and Nodes // contains a mixture of Blocks and Nodes
if ((block as Block).render) return (block as Block).render(); if ((block as Block).render) return (block as Block).render();
return block; return block;
})); })
);
if (options.dev && !options.hydratable) { if (options.dev && !options.hydratable) {
block.chunks.claim.push( block.chunks.claim.push(
@ -85,31 +97,52 @@ export default function dom(
`; `;
} }
const uses_props = component.var_lookup.has('$$props'); const uses_props = component.var_lookup.has('$$props');
const uses_rest = component.var_lookup.has('$$restProps'); const uses_rest = component.var_lookup.has('$$restProps');
const $$props = uses_props || uses_rest ? '$$new_props' : '$$props'; const $$props = uses_props || uses_rest ? '$$new_props' : '$$props';
const props = component.vars.filter(variable => !variable.module && variable.export_name); const props = component.vars.filter((variable) => !variable.module && variable.export_name);
const writable_props = props.filter(variable => variable.writable); const writable_props = props.filter((variable) => variable.writable);
const omit_props_names = component.get_unique_name('omit_props_names'); const omit_props_names = component.get_unique_name('omit_props_names');
const compute_rest = x`@compute_rest_props($$props, ${omit_props_names.name})`; const compute_rest = x`@compute_rest_props($$props, ${omit_props_names.name})`;
const rest = uses_rest ? b` const rest = uses_rest
const ${omit_props_names.name} = [${props.map(prop => `"${prop.export_name}"`).join(',')}]; ? b`
const ${omit_props_names.name} = [${props.map((prop) => `"${prop.export_name}"`).join(',')}];
let $$restProps = ${compute_rest}; let $$restProps = ${compute_rest};
` : null; `
: null;
const set = (uses_props || uses_rest || writable_props.length > 0 || component.slots.size > 0) const set =
uses_props || uses_rest || writable_props.length > 0 || component.slots.size > 0
? x` ? x`
${$$props} => { ${$$props} => {
${uses_props && renderer.invalidate('$$props', x`$$props = @assign(@assign({}, $$props), @exclude_internal_props($$new_props))`)} ${
${uses_rest && !uses_props && x`$$props = @assign(@assign({}, $$props), @exclude_internal_props($$new_props))`} uses_props &&
renderer.invalidate(
'$$props',
x`$$props = @assign(@assign({}, $$props), @exclude_internal_props($$new_props))`
)
}
${
uses_rest &&
!uses_props &&
x`$$props = @assign(@assign({}, $$props), @exclude_internal_props($$new_props))`
}
${uses_rest && renderer.invalidate('$$restProps', x`$$restProps = ${compute_rest}`)} ${uses_rest && renderer.invalidate('$$restProps', x`$$restProps = ${compute_rest}`)}
${writable_props.map(prop => ${writable_props.map(
b`if ('${prop.export_name}' in ${$$props}) ${renderer.invalidate(prop.name, x`${prop.name} = ${$$props}.${prop.export_name}`)};` (prop) =>
b`if ('${prop.export_name}' in ${$$props}) ${renderer.invalidate(
prop.name,
x`${prop.name} = ${$$props}.${prop.export_name}`
)};`
)} )}
${component.slots.size > 0 && ${
b`if ('$$scope' in ${$$props}) ${renderer.invalidate('$$scope', x`$$scope = ${$$props}.$$scope`)};`} component.slots.size > 0 &&
b`if ('$$scope' in ${$$props}) ${renderer.invalidate(
'$$scope',
x`$$scope = ${$$props}.$$scope`
)};`
}
} }
` `
: null; : null;
@ -122,7 +155,7 @@ export default function dom(
let capture_state: Expression; let capture_state: Expression;
let props_inject: Node[] | Node; let props_inject: Node[] | Node;
props.forEach(prop => { props.forEach((prop) => {
const variable = component.var_lookup.get(prop.name); const variable = component.var_lookup.get(prop.name);
if (!variable.writable || component.component_options.accessors) { if (!variable.writable || component.component_options.accessors) {
@ -131,7 +164,11 @@ export default function dom(
kind: 'get', kind: 'get',
key: { type: 'Identifier', name: prop.export_name }, key: { type: 'Identifier', name: prop.export_name },
value: x`function() { value: x`function() {
return ${prop.hoistable ? prop.name : x`this.$$.ctx[${renderer.context_lookup.get(prop.name).index}]`} return ${
prop.hoistable
? prop.name
: x`this.$$.ctx[${renderer.context_lookup.get(prop.name).index}]`
}
}` }`
}); });
} else if (component.compile_options.dev) { } else if (component.compile_options.dev) {
@ -178,7 +215,7 @@ export default function dom(
} }
}); });
component.instance_exports_from.forEach(exports_from => { component.instance_exports_from.forEach((exports_from) => {
const import_declaration = { const import_declaration = {
...exports_from, ...exports_from,
type: 'ImportDeclaration', type: 'ImportDeclaration',
@ -187,7 +224,7 @@ export default function dom(
}; };
component.imports.push(import_declaration as ImportDeclaration); component.imports.push(import_declaration as ImportDeclaration);
exports_from.specifiers.forEach(specifier => { exports_from.specifiers.forEach((specifier) => {
if (component.component_options.accessors) { if (component.component_options.accessors) {
const name = component.get_unique_name(specifier.exported.name); const name = component.get_unique_name(specifier.exported.name);
import_declaration.specifiers.push({ import_declaration.specifiers.push({
@ -220,33 +257,46 @@ export default function dom(
if (component.compile_options.dev) { if (component.compile_options.dev) {
// checking that expected ones were passed // checking that expected ones were passed
const expected = props.filter(prop => prop.writable && !prop.initialised); const expected = props.filter((prop) => prop.writable && !prop.initialised);
if (expected.length) { if (expected.length) {
missing_props_check = b` missing_props_check = b`
$$self.$$.on_mount.push(function () { $$self.$$.on_mount.push(function () {
${expected.map(prop => b` ${expected.map(
(prop) => b`
if (${prop.name} === undefined && !(('${prop.export_name}' in $$props) || $$self.$$.bound[$$self.$$.props['${prop.export_name}']])) { if (${prop.name} === undefined && !(('${prop.export_name}' in $$props) || $$self.$$.bound[$$self.$$.props['${prop.export_name}']])) {
@_console.warn("<${component.tag}> was created without expected prop '${prop.export_name}'"); @_console.warn("<${component.tag}> was created without expected prop '${prop.export_name}'");
}`)} }`
)}
}); });
`; `;
} }
const capturable_vars = component.vars.filter(v => !v.internal && !v.global && !v.name.startsWith('$$')); const capturable_vars = component.vars.filter(
(v) => !v.internal && !v.global && !v.name.startsWith('$$')
);
if (capturable_vars.length > 0) { if (capturable_vars.length > 0) {
capture_state = x`() => ({ ${capturable_vars.map(prop => p`${prop.name}`)} })`; capture_state = x`() => ({ ${capturable_vars.map((prop) => p`${prop.name}`)} })`;
} }
const injectable_vars = capturable_vars.filter(v => !v.module && v.writable && v.name[0] !== '$'); const injectable_vars = capturable_vars.filter(
(v) => !v.module && v.writable && v.name[0] !== '$'
);
if (uses_props || injectable_vars.length > 0) { if (uses_props || injectable_vars.length > 0) {
inject_state = x` inject_state = x`
${$$props} => { ${$$props} => {
${uses_props && renderer.invalidate('$$props', x`$$props = @assign(@assign({}, $$props), $$new_props)`)} ${
uses_props &&
renderer.invalidate('$$props', x`$$props = @assign(@assign({}, $$props), $$new_props)`)
}
${injectable_vars.map( ${injectable_vars.map(
v => b`if ('${v.name}' in $$props) ${renderer.invalidate(v.name, x`${v.name} = ${$$props}.${v.name}`)};` (v) =>
b`if ('${v.name}' in $$props) ${renderer.invalidate(
v.name,
x`${v.name} = ${$$props}.${v.name}`
)};`
)} )}
} }
`; `;
@ -273,7 +323,11 @@ export default function dom(
if (!execution_context && !scope.block) { if (!execution_context && !scope.block) {
execution_context = node; execution_context = node;
} }
} else if (!execution_context && node.type === 'LabeledStatement' && node.label.name === '$') { } else if (
!execution_context &&
node.type === 'LabeledStatement' &&
node.label.name === '$'
) {
execution_context = node; execution_context = node;
} }
}, },
@ -305,7 +359,8 @@ export default function dom(
const value = `$${name}`; const value = `$${name}`;
const i = renderer.context_lookup.get(`$${name}`).index; const i = renderer.context_lookup.get(`$${name}`).index;
const insert = (reassigned || export_name) const insert =
reassigned || export_name
? b`${`$$subscribe_${name}`}()` ? b`${`$$subscribe_${name}`}()`
: b`@component_subscribe($$self, ${name}, #value => $$invalidate(${i}, ${value} = #value))`; : b`@component_subscribe($$self, ${name}, #value => $$invalidate(${i}, ${value} = #value))`;
@ -318,7 +373,8 @@ export default function dom(
} }
const args = [x`$$self`]; const args = [x`$$self`];
const has_invalidate = props.length > 0 || const has_invalidate =
props.length > 0 ||
component.has_reactive_assignments || component.has_reactive_assignments ||
component.slots.size > 0 || component.slots.size > 0 ||
capture_state || capture_state ||
@ -345,18 +401,20 @@ export default function dom(
${component.fully_hoisted} ${component.fully_hoisted}
`); `);
const filtered_props = props.filter(prop => { const filtered_props = props.filter((prop) => {
const variable = component.var_lookup.get(prop.name); const variable = component.var_lookup.get(prop.name);
if (variable.hoistable) return false; if (variable.hoistable) return false;
return prop.name[0] !== '$'; return prop.name[0] !== '$';
}); });
const reactive_stores = component.vars.filter(variable => variable.name[0] === '$' && variable.name[1] !== '$'); const reactive_stores = component.vars.filter(
(variable) => variable.name[0] === '$' && variable.name[1] !== '$'
);
const instance_javascript = component.extract_javascript(component.ast.instance); const instance_javascript = component.extract_javascript(component.ast.instance);
const has_definition = ( const has_definition =
component.compile_options.dev || component.compile_options.dev ||
(instance_javascript && instance_javascript.length > 0) || (instance_javascript && instance_javascript.length > 0) ||
filtered_props.length > 0 || filtered_props.length > 0 ||
@ -365,44 +423,48 @@ export default function dom(
renderer.initial_context.length > 0 || renderer.initial_context.length > 0 ||
component.reactive_declarations.length > 0 || component.reactive_declarations.length > 0 ||
capture_state || capture_state ||
inject_state inject_state;
);
const definition = has_definition const definition = has_definition
? component.alias('instance') ? component.alias('instance')
: { type: 'Literal', value: null }; : { type: 'Literal', value: null };
const reactive_store_subscriptions = reactive_stores const reactive_store_subscriptions = reactive_stores
.filter(store => { .filter((store) => {
const variable = component.var_lookup.get(store.name.slice(1)); const variable = component.var_lookup.get(store.name.slice(1));
return !variable || variable.hoistable; return !variable || variable.hoistable;
}) })
.map(({ name }) => b` .map(
({ name }) => b`
${component.compile_options.dev && b`@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(${renderer.context_lookup.get(name).index}, ${name} = $$value)); @component_subscribe($$self, ${name.slice(1)}, $$value => $$invalidate(${
`); renderer.context_lookup.get(name).index
}, ${name} = $$value));
`
);
const resubscribable_reactive_store_unsubscribers = reactive_stores const resubscribable_reactive_store_unsubscribers = reactive_stores
.filter(store => { .filter((store) => {
const variable = component.var_lookup.get(store.name.slice(1)); const variable = component.var_lookup.get(store.name.slice(1));
return variable && (variable.reassigned || variable.export_name); return variable && (variable.reassigned || variable.export_name);
}) })
.map(({ name }) => b`$$self.$$.on_destroy.push(() => ${`$$unsubscribe_${name.slice(1)}`}());`); .map(({ name }) => b`$$self.$$.on_destroy.push(() => ${`$$unsubscribe_${name.slice(1)}`}());`);
if (has_definition) { if (has_definition) {
const reactive_declarations: (Node | Node[]) = []; const reactive_declarations: Node | Node[] = [];
const fixed_reactive_declarations: Node[] = []; // not really 'reactive' but whatever const fixed_reactive_declarations: Node[] = []; // not really 'reactive' but whatever
component.reactive_declarations.forEach(d => { component.reactive_declarations.forEach((d) => {
const dependencies = Array.from(d.dependencies); const dependencies = Array.from(d.dependencies);
const uses_rest_or_props = !!dependencies.find(n => n === '$$props' || n === '$$restProps'); const uses_rest_or_props = !!dependencies.find((n) => n === '$$props' || n === '$$restProps');
const writable = dependencies.filter(n => { const writable = dependencies.filter((n) => {
const variable = component.var_lookup.get(n); const variable = component.var_lookup.get(n);
return variable && (variable.export_name || variable.mutated || variable.reassigned); return variable && (variable.export_name || variable.mutated || variable.reassigned);
}); });
const condition = !uses_rest_or_props && writable.length > 0 && renderer.dirty(writable, true); const condition =
!uses_rest_or_props && writable.length > 0 && renderer.dirty(writable, true);
let statement = d.node; // TODO remove label (use d.node.body) if it's not referenced let statement = d.node; // TODO remove label (use d.node.body) if it's not referenced
@ -415,12 +477,12 @@ export default function dom(
} }
}); });
const injected = Array.from(component.injected_reactive_declaration_vars).filter(name => { const injected = Array.from(component.injected_reactive_declaration_vars).filter((name) => {
const variable = component.var_lookup.get(name); const variable = component.var_lookup.get(name);
return variable.injected && variable.name[0] !== '$'; return variable.injected && variable.name[0] !== '$';
}); });
const reactive_store_declarations = reactive_stores.map(variable => { const reactive_store_declarations = reactive_stores.map((variable) => {
const $name = variable.name; const $name = variable.name;
const name = $name.slice(1); const name = $name.slice(1);
@ -439,24 +501,29 @@ export default function dom(
let unknown_props_check: Node[] | undefined; let unknown_props_check: Node[] | undefined;
if (component.compile_options.dev && !(uses_props || uses_rest)) { if (component.compile_options.dev && !(uses_props || uses_rest)) {
unknown_props_check = b` unknown_props_check = b`
const writable_props = [${writable_props.map(prop => x`'${prop.export_name}'`)}]; const writable_props = [${writable_props.map((prop) => x`'${prop.export_name}'`)}];
@_Object.keys($$props).forEach(key => { @_Object.keys($$props).forEach(key => {
if (!~writable_props.indexOf(key) && key.slice(0, 2) !== '$$' && key !== 'slot') @_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}'\`);
}); });
`; `;
} }
const return_value = { const return_value = {
type: 'ArrayExpression', type: 'ArrayExpression',
elements: renderer.initial_context.map(member => ({ elements: renderer.initial_context.map(
(member) =>
({
type: 'Identifier', type: 'Identifier',
name: member.name name: member.name
}) as Expression) } as Expression)
)
}; };
body.push(b` body.push(b`
function ${definition}(${args}) { function ${definition}(${args}) {
${injected.map(name => b`let ${name};`)} ${injected.map((name) => b`let ${name};`)}
${rest} ${rest}
@ -466,8 +533,17 @@ export default function dom(
${resubscribable_reactive_store_unsubscribers} ${resubscribable_reactive_store_unsubscribers}
${component.slots.size || component.compile_options.dev || uses_slots ? b`let { $$slots: #slots = {}, $$scope } = $$props;` : null} ${
${component.compile_options.dev && b`@validate_slots('${component.tag}', #slots, [${[...component.slots.keys()].map(key => `'${key}'`).join(',')}]);`} component.slots.size || component.compile_options.dev || uses_slots
? b`let { $$slots: #slots = {}, $$scope } = $$props;`
: null
}
${
component.compile_options.dev &&
b`@validate_slots('${component.tag}', #slots, [${[...component.slots.keys()]
.map((key) => `'${key}'`)
.join(',')}]);`
}
${compute_slots} ${compute_slots}
${instance_javascript} ${instance_javascript}
@ -475,7 +551,10 @@ export default function dom(
${missing_props_check} ${missing_props_check}
${unknown_props_check} ${unknown_props_check}
${renderer.binding_groups.size > 0 && b`const $$binding_groups = [${[...renderer.binding_groups.keys()].map(_ => x`[]`)}];`} ${
renderer.binding_groups.size > 0 &&
b`const $$binding_groups = [${[...renderer.binding_groups.keys()].map((_) => x`[]`)}];`
}
${component.partly_hoisted} ${component.partly_hoisted}
@ -487,11 +566,14 @@ export default function dom(
${/* before reactive declarations */ props_inject} ${/* before reactive declarations */ props_inject}
${reactive_declarations.length > 0 && b` ${
reactive_declarations.length > 0 &&
b`
$$self.$$.update = () => { $$self.$$.update = () => {
${reactive_declarations} ${reactive_declarations}
}; };
`} `
}
${fixed_reactive_declarations} ${fixed_reactive_declarations}
@ -503,7 +585,9 @@ export default function dom(
} }
const prop_indexes = x`{ const prop_indexes = x`{
${props.filter(v => v.export_name && !v.module).map(v => p`${v.export_name}: ${renderer.context_lookup.get(v.name).index}`)} ${props
.filter((v) => v.export_name && !v.module)
.map((v) => p`${v.export_name}: ${renderer.context_lookup.get(v.name).index}`)}
}` as ObjectExpression; }` as ObjectExpression;
let dirty; let dirty;
@ -533,8 +617,13 @@ export default function dom(
class ${name} extends ${superclass} { class ${name} extends ${superclass} {
constructor(options) { constructor(options) {
super(${options.dev && 'options'}); super(${options.dev && 'options'});
@init(this, options, ${definition}, ${has_create_fragment ? 'create_fragment' : 'null'}, ${not_equal}, ${prop_indexes}, ${optional_parameters}); @init(this, options, ${definition}, ${
${options.dev && b`@dispatch_dev("SvelteRegisterComponent", { component: this, tagName: "${name.name}", options, id: create_fragment.name });`} has_create_fragment ? 'create_fragment' : 'null'
}, ${not_equal}, ${prop_indexes}, ${optional_parameters});
${
options.dev &&
b`@dispatch_dev("SvelteRegisterComponent", { component: this, tagName: "${name.name}", options, id: create_fragment.name });`
}
} }
} }
`[0] as ClassDeclaration; `[0] as ClassDeclaration;
@ -544,25 +633,35 @@ export default function dom(
if (options.customElement) { if (options.customElement) {
const props_str = writable_props.reduce((def, prop) => { const props_str = writable_props.reduce((def, prop) => {
def[prop.export_name] = component.component_options.customElement?.props?.[prop.export_name] || {}; def[prop.export_name] =
component.component_options.customElement?.props?.[prop.export_name] || {};
if (prop.is_boolean && !def[prop.export_name].type) { if (prop.is_boolean && !def[prop.export_name].type) {
def[prop.export_name].type = 'Boolean'; def[prop.export_name].type = 'Boolean';
} }
return def; return def;
}, {}); }, {});
const slots_str = [...component.slots.keys()].map(key => `"${key}"`).join(','); const slots_str = [...component.slots.keys()].map((key) => `"${key}"`).join(',');
const accessors_str = accessors const accessors_str = accessors
.filter(accessor => !writable_props.some(prop => prop.export_name === accessor.key.name)) .filter((accessor) => !writable_props.some((prop) => prop.export_name === accessor.key.name))
.map(accessor => `"${accessor.key.name}"`) .map((accessor) => `"${accessor.key.name}"`)
.join(','); .join(',');
const use_shadow_dom = component.component_options.customElement?.shadow !== 'none' ? 'true' : 'false'; const use_shadow_dom =
component.component_options.customElement?.shadow !== 'none' ? 'true' : 'false';
if (component.component_options.customElement?.tag) { if (component.component_options.customElement?.tag) {
body.push( body.push(
b`@_customElements.define("${component.component_options.customElement.tag}", @create_custom_element(${name}, ${JSON.stringify(props_str)}, [${slots_str}], [${accessors_str}], ${use_shadow_dom}));` b`@_customElements.define("${
component.component_options.customElement.tag
}", @create_custom_element(${name}, ${JSON.stringify(
props_str
)}, [${slots_str}], [${accessors_str}], ${use_shadow_dom}));`
); );
} else { } else {
body.push(b`@create_custom_element(${name}, ${JSON.stringify(props_str)}, [${slots_str}], [${accessors_str}], ${use_shadow_dom});`); body.push(
b`@create_custom_element(${name}, ${JSON.stringify(
props_str
)}, [${slots_str}], [${accessors_str}], ${use_shadow_dom});`
);
} }
} }

@ -5,27 +5,32 @@ import { Node, Expression } from 'estree';
import Renderer from './Renderer'; import Renderer from './Renderer';
import { Var } from '../../interfaces'; import { Var } from '../../interfaces';
export function invalidate(renderer: Renderer, scope: Scope, node: Node, names: Set<string>, main_execution_context: boolean = false) { export function invalidate(
renderer: Renderer,
scope: Scope,
node: Node,
names: Set<string>,
main_execution_context: boolean = false
) {
const { component } = renderer; const { component } = renderer;
const [head, ...tail] = Array.from(names) const [head, ...tail] = Array.from(names)
.filter(name => { .filter((name) => {
const owner = scope.find_owner(name); const owner = scope.find_owner(name);
return !owner || owner === component.instance_scope; return !owner || owner === component.instance_scope;
}) })
.map(name => component.var_lookup.get(name)) .map((name) => component.var_lookup.get(name))
.filter(variable => { .filter((variable) => {
return variable && ( return (
variable &&
!variable.hoistable && !variable.hoistable &&
!variable.global && !variable.global &&
!variable.module && !variable.module &&
( (variable.referenced ||
variable.referenced ||
variable.subscribable || variable.subscribable ||
variable.is_reactive_dependency || variable.is_reactive_dependency ||
variable.export_name || variable.export_name ||
variable.name[0] === '$' variable.name[0] === '$')
)
); );
}) as Var[]; }) as Var[];
@ -42,12 +47,17 @@ export function invalidate(renderer: Renderer, scope: Scope, node: Node, names:
component.has_reactive_assignments = true; component.has_reactive_assignments = true;
if (node.type === 'AssignmentExpression' && node.operator === '=' && nodes_match(node.left, node.right) && tail.length === 0) { if (
node.type === 'AssignmentExpression' &&
node.operator === '=' &&
nodes_match(node.left, node.right) &&
tail.length === 0
) {
return get_invalidated(head, node); return get_invalidated(head, node);
} }
const is_store_value = head.name[0] === '$' && head.name[1] !== '$'; const is_store_value = head.name[0] === '$' && head.name[1] !== '$';
const extra_args = tail.map(variable => get_invalidated(variable)).filter(Boolean); const extra_args = tail.map((variable) => get_invalidated(variable)).filter(Boolean);
if (is_store_value) { if (is_store_value) {
return x`@set_store_value(${head.name.slice(1)}, ${node}, ${head.name}, ${extra_args})`; return x`@set_store_value(${head.name.slice(1)}, ${node}, ${head.name}, ${extra_args})`;
@ -55,18 +65,19 @@ export function invalidate(renderer: Renderer, scope: Scope, node: Node, names:
let invalidate; let invalidate;
if (!main_execution_context) { if (!main_execution_context) {
const pass_value = ( const pass_value =
extra_args.length > 0 || extra_args.length > 0 ||
(node.type === 'AssignmentExpression' && node.left.type !== 'Identifier') || (node.type === 'AssignmentExpression' && node.left.type !== 'Identifier') ||
(node.type === 'UpdateExpression' && (!node.prefix || node.argument.type !== 'Identifier')) (node.type === 'UpdateExpression' && (!node.prefix || node.argument.type !== 'Identifier'));
);
if (pass_value) { if (pass_value) {
extra_args.unshift({ extra_args.unshift({
type: 'Identifier', type: 'Identifier',
name: head.name name: head.name
}); });
} }
invalidate = x`$$invalidate(${renderer.context_lookup.get(head.name).index}, ${node}, ${extra_args})`; invalidate = x`$$invalidate(${
renderer.context_lookup.get(head.name).index
}, ${node}, ${extra_args})`;
} else { } else {
// skip `$$invalidate` if it is in the main execution context // skip `$$invalidate` if it is in the main execution context
invalidate = extra_args.length ? [node, ...extra_args] : node; invalidate = extra_args.length ? [node, ...extra_args] : node;
@ -80,10 +91,15 @@ export function invalidate(renderer: Renderer, scope: Scope, node: Node, names:
return invalidate; return invalidate;
} }
export function renderer_invalidate(renderer: Renderer, name: string, value?: unknown, main_execution_context: boolean = false) { export function renderer_invalidate(
renderer: Renderer,
name: string,
value?: unknown,
main_execution_context: boolean = false
) {
const variable = renderer.component.var_lookup.get(name); const variable = renderer.component.var_lookup.get(name);
if (variable && (variable.subscribable && (variable.reassigned || variable.export_name))) { if (variable && variable.subscribable && (variable.reassigned || variable.export_name)) {
if (main_execution_context) { if (main_execution_context) {
return x`${`$$subscribe_${name}`}(${value || name})`; return x`${`$$subscribe_${name}`}(${value || name})`;
} else { } else {
@ -97,14 +113,12 @@ export function renderer_invalidate(renderer: Renderer, name: string, value?: un
} }
if ( if (
variable && ( variable &&
variable.module || ( (variable.module ||
!variable.referenced && (!variable.referenced &&
!variable.is_reactive_dependency && !variable.is_reactive_dependency &&
!variable.export_name && !variable.export_name &&
!name.startsWith('$$') !name.startsWith('$$')))
)
)
) { ) {
return value || name; return value || name;
} }
@ -123,22 +137,22 @@ export function renderer_invalidate(renderer: Renderer, name: string, value?: un
// if this is a reactive declaration, invalidate dependencies recursively // if this is a reactive declaration, invalidate dependencies recursively
const deps = new Set([name]); const deps = new Set([name]);
deps.forEach(name => { deps.forEach((name) => {
const reactive_declarations = renderer.component.reactive_declarations.filter(x => const reactive_declarations = renderer.component.reactive_declarations.filter((x) =>
x.assignees.has(name) x.assignees.has(name)
); );
reactive_declarations.forEach(declaration => { reactive_declarations.forEach((declaration) => {
declaration.dependencies.forEach(name => { declaration.dependencies.forEach((name) => {
deps.add(name); deps.add(name);
}); });
}); });
}); });
// TODO ideally globals etc wouldn't be here in the first place // TODO ideally globals etc wouldn't be here in the first place
const filtered = Array.from(deps).filter(n => renderer.context_lookup.has(n)); const filtered = Array.from(deps).filter((n) => renderer.context_lookup.has(n));
if (!filtered.length) return null; if (!filtered.length) return null;
return filtered return filtered
.map(n => x`$$invalidate(${renderer.context_lookup.get(n).index}, ${n})`) .map((n) => x`$$invalidate(${renderer.context_lookup.get(n).index}, ${n})`)
.reduce((lhs, rhs) => x`${lhs}, ${rhs}`); .reduce((lhs, rhs) => x`${lhs}, ${rhs}`);
} }

@ -69,7 +69,7 @@ class AwaitBlockBranch extends Wrapper {
this.value = node.name; this.value = node.name;
this.renderer.add_to_context(this.value, true); this.renderer.add_to_context(this.value, true);
} else { } else {
contexts.forEach(context => { contexts.forEach((context) => {
if (context.type !== 'DestructuredVariable') return; if (context.type !== 'DestructuredVariable') return;
this.renderer.add_to_context(context.key.name, true); this.renderer.add_to_context(context.key.name, true);
}); });
@ -98,17 +98,28 @@ class AwaitBlockBranch extends Wrapper {
} }
render_get_context() { render_get_context() {
const props = this.is_destructured ? this.value_contexts.map(prop => { const props = this.is_destructured
? this.value_contexts.map((prop) => {
if (prop.type === 'ComputedProperty') { if (prop.type === 'ComputedProperty') {
const expression = new Expression(this.renderer.component, this.node, this.has_consts(this.node) ? this.node.scope : null, prop.key); const expression = new Expression(
this.renderer.component,
this.node,
this.has_consts(this.node) ? this.node.scope : null,
prop.key
);
return b`const ${prop.property_name} = ${expression.manipulate(this.block, '#ctx')};`; return b`const ${prop.property_name} = ${expression.manipulate(this.block, '#ctx')};`;
} else { } else {
const to_ctx = name => this.renderer.reference(name); const to_ctx = (name) => this.renderer.reference(name);
return b`#ctx[${this.block.renderer.context_lookup.get(prop.key.name).index}] = ${prop.default_modifier(prop.modifier(x`#ctx[${this.value_index}]`), to_ctx)};`; return b`#ctx[${
this.block.renderer.context_lookup.get(prop.key.name).index
}] = ${prop.default_modifier(prop.modifier(x`#ctx[${this.value_index}]`), to_ctx)};`;
} }
}) : null; })
: null;
const const_tags_props = this.has_consts(this.node) ? add_const_tags(this.block, this.node.const_tags, '#ctx') : null; const const_tags_props = this.has_consts(this.node)
? add_const_tags(this.block, this.node.const_tags, '#ctx')
: null;
const get_context = this.block.renderer.component.get_unique_name(`get_${this.status}_context`); const get_context = this.block.renderer.component.get_unique_name(`get_${this.status}_context`);
this.block.renderer.blocks.push(b` this.block.renderer.blocks.push(b`
@ -176,7 +187,7 @@ export default class AwaitBlockWrapper extends Wrapper {
this[status] = branch; this[status] = branch;
}); });
['pending', 'then', 'catch'].forEach(status => { ['pending', 'then', 'catch'].forEach((status) => {
this[status].block.has_update_method = is_dynamic; this[status].block.has_update_method = is_dynamic;
this[status].block.has_intro_method = has_intros; this[status].block.has_intro_method = has_intros;
this[status].block.has_outro_method = has_outros; this[status].block.has_outro_method = has_outros;
@ -187,11 +198,7 @@ export default class AwaitBlockWrapper extends Wrapper {
} }
} }
render( render(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
block: Block,
parent_node: Identifier,
parent_nodes: Identifier
) {
const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes); const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes);
const update_mount_node = this.get_update_mount_node(anchor); const update_mount_node = this.get_update_mount_node(anchor);
@ -238,7 +245,8 @@ export default class AwaitBlockWrapper extends Wrapper {
const initial_mount_node = parent_node || '#target'; const initial_mount_node = parent_node || '#target';
const anchor_node = parent_node ? 'null' : '#anchor'; const anchor_node = parent_node ? 'null' : '#anchor';
const has_transitions = this.pending.block.has_intro_method || this.pending.block.has_outro_method; const has_transitions =
this.pending.block.has_intro_method || this.pending.block.has_outro_method;
block.chunks.mount.push(b` block.chunks.mount.push(b`
${info}.block.m(${initial_mount_node}, ${info}.anchor = ${anchor_node}); ${info}.block.m(${initial_mount_node}, ${info}.anchor = ${anchor_node});
@ -260,9 +268,7 @@ export default class AwaitBlockWrapper extends Wrapper {
${promise} !== (${promise} = ${snippet}) && ${promise} !== (${promise} = ${snippet}) &&
@handle_promise(${promise}, ${info})`; @handle_promise(${promise}, ${info})`;
block.chunks.update.push( block.chunks.update.push(b`${info}.ctx = #ctx;`);
b`${info}.ctx = #ctx;`
);
if (this.pending.block.has_update_method) { if (this.pending.block.has_update_method) {
block.chunks.update.push(b` block.chunks.update.push(b`
@ -300,7 +306,7 @@ export default class AwaitBlockWrapper extends Wrapper {
${info} = null; ${info} = null;
`); `);
[this.pending, this.then, this.catch].forEach(branch => { [this.pending, this.then, this.catch].forEach((branch) => {
branch.render(branch.block, null, x`#nodes` as Identifier); branch.render(branch.block, null, x`#nodes` as Identifier);
}); });
} }

@ -15,7 +15,7 @@ export default class BodyWrapper extends Wrapper {
constructor(renderer: Renderer, block: Block, parent: Wrapper, node: TemplateNode) { constructor(renderer: Renderer, block: Block, parent: Wrapper, node: TemplateNode) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.handlers = this.node.handlers.map(handler => new EventHandler(handler, this)); this.handlers = this.node.handlers.map((handler) => new EventHandler(handler, this));
} }
render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) { render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) {

@ -9,12 +9,7 @@ export default class CommentWrapper extends Wrapper {
node: Comment; node: Comment;
var: Identifier; var: Identifier;
constructor( constructor(renderer: Renderer, block: Block, parent: Wrapper, node: Comment) {
renderer: Renderer,
block: Block,
parent: Wrapper,
node: Comment
) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.var = x`c` as Identifier; this.var = x`c` as Identifier;
} }

@ -50,21 +50,23 @@ export default class DebugTagWrapper extends Wrapper {
}; };
const dependencies: Set<string> = new Set(); const dependencies: Set<string> = new Set();
this.node.expressions.forEach(expression => { this.node.expressions.forEach((expression) => {
add_to_set(dependencies, expression.dependencies); add_to_set(dependencies, expression.dependencies);
}); });
const contextual_identifiers = this.node.expressions const contextual_identifiers = this.node.expressions
.filter(e => { .filter((e) => {
const variable = var_lookup.get((e.node as Identifier).name); const variable = var_lookup.get((e.node as Identifier).name);
return !(variable && variable.hoistable); return !(variable && variable.hoistable);
}) })
.map(e => (e.node as Identifier).name); .map((e) => (e.node as Identifier).name);
const logged_identifiers = this.node.expressions.map(e => p`${(e.node as Identifier).name}`); const logged_identifiers = this.node.expressions.map(
(e) => p`${(e.node as Identifier).name}`
);
const debug_statements = b` const debug_statements = b`
${contextual_identifiers.map(name => b`const ${name} = ${renderer.reference(name)};`)} ${contextual_identifiers.map((name) => b`const ${name} = ${renderer.reference(name)};`)}
@_console.${log}({ ${logged_identifiers} }); @_console.${log}({ ${logged_identifiers} });
debugger;`; debugger;`;

@ -14,10 +14,7 @@ const associated_events = {
visibilityState: ['visibilitychange'] visibilityState: ['visibilitychange']
}; };
const readonly = new Set([ const readonly = new Set(['fullscreenElement', 'visibilityState']);
'fullscreenElement',
'visibilityState'
]);
export default class DocumentWrapper extends Wrapper { export default class DocumentWrapper extends Wrapper {
node: Document; node: Document;
@ -25,7 +22,7 @@ export default class DocumentWrapper extends Wrapper {
constructor(renderer: Renderer, block: Block, parent: Wrapper, node: TemplateNode) { constructor(renderer: Renderer, block: Block, parent: Wrapper, node: TemplateNode) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.handlers = this.node.handlers.map(handler => new EventHandler(handler, this)); this.handlers = this.node.handlers.map((handler) => new EventHandler(handler, this));
} }
render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) { render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) {
@ -38,7 +35,7 @@ export default class DocumentWrapper extends Wrapper {
add_event_handlers(block, x`@_document`, this.handlers); add_event_handlers(block, x`@_document`, this.handlers);
add_actions(block, x`@_document`, this.node.actions); add_actions(block, x`@_document`, this.node.actions);
this.node.bindings.forEach(binding => { this.node.bindings.forEach((binding) => {
// TODO: what if it's a MemberExpression? // TODO: what if it's a MemberExpression?
const binding_name = (binding.expression.node as Identifier).name; const binding_name = (binding.expression.node as Identifier).name;
@ -52,7 +49,7 @@ export default class DocumentWrapper extends Wrapper {
const binding_events = associated_events[binding.name]; const binding_events = associated_events[binding.name];
const property = binding.name; const property = binding.name;
binding_events.forEach(associated_event => { binding_events.forEach((associated_event) => {
if (!events[associated_event]) events[associated_event] = []; if (!events[associated_event]) events[associated_event] = [];
events[associated_event].push({ events[associated_event].push({
name: binding_name, name: binding_name,
@ -61,17 +58,15 @@ export default class DocumentWrapper extends Wrapper {
}); });
}); });
Object.keys(events).forEach(event => { Object.keys(events).forEach((event) => {
const id = block.get_unique_name(`ondocument${event}`); const id = block.get_unique_name(`ondocument${event}`);
const props = events[event]; const props = events[event];
renderer.add_to_context(id.name); renderer.add_to_context(id.name);
const fn = renderer.reference(id.name); const fn = renderer.reference(id.name);
props.forEach(prop => { props.forEach((prop) => {
renderer.meta_bindings.push( renderer.meta_bindings.push(b`this._state.${prop.name} = @_document.${prop.value};`);
b`this._state.${prop.name} = @_document.${prop.value};`
);
}); });
block.event_listeners.push(x` block.event_listeners.push(x`
@ -80,7 +75,7 @@ export default class DocumentWrapper extends Wrapper {
component.partly_hoisted.push(b` component.partly_hoisted.push(b`
function ${id}() { function ${id}() {
${props.map(prop => renderer.invalidate(prop.name, x`${prop.name} = @_document.${prop.value}`))} ${props.map((prop) => renderer.invalidate(prop.name, x`${prop.name} = @_document.${prop.value}`))}
} }
`); `);

@ -84,7 +84,7 @@ export default class EachBlockWrapper extends Wrapper {
const { dependencies } = node.expression; const { dependencies } = node.expression;
block.add_dependencies(dependencies); block.add_dependencies(dependencies);
this.node.contexts.forEach(context => { this.node.contexts.forEach((context) => {
if (context.type !== 'DestructuredVariable') return; if (context.type !== 'DestructuredVariable') return;
renderer.add_to_context(context.key.name, true); renderer.add_to_context(context.key.name, true);
}); });
@ -109,7 +109,7 @@ export default class EachBlockWrapper extends Wrapper {
const fixed_length = const fixed_length =
node.expression.node.type === 'ArrayExpression' && node.expression.node.type === 'ArrayExpression' &&
node.expression.node.elements.every(element => element.type !== 'SpreadElement') node.expression.node.elements.every((element) => element.type !== 'SpreadElement')
? node.expression.node.elements.length ? node.expression.node.elements.length
: null; : null;
@ -144,9 +144,10 @@ export default class EachBlockWrapper extends Wrapper {
}; };
const object = get_object(node.expression.node); const object = get_object(node.expression.node);
const store = object.type === 'Identifier' && object.name[0] === '$' ? object.name.slice(1) : null; const store =
object.type === 'Identifier' && object.name[0] === '$' ? object.name.slice(1) : null;
node.contexts.forEach(prop => { node.contexts.forEach((prop) => {
if (prop.type !== 'DestructuredVariable') return; if (prop.type !== 'DestructuredVariable') return;
this.block.bindings.set(prop.key.name, { this.block.bindings.set(prop.key.name, {
object: this.vars.each_block_value, object: this.vars.each_block_value,
@ -163,7 +164,14 @@ export default class EachBlockWrapper extends Wrapper {
renderer.blocks.push(this.block); renderer.blocks.push(this.block);
this.fragment = new FragmentWrapper(renderer, this.block, node.children, this, strip_whitespace, next_sibling); this.fragment = new FragmentWrapper(
renderer,
this.block,
node.children,
this,
strip_whitespace,
next_sibling
);
if (this.node.else) { if (this.node.else) {
this.else = new ElseBlockWrapper( this.else = new ElseBlockWrapper(
@ -196,8 +204,8 @@ export default class EachBlockWrapper extends Wrapper {
const { component } = renderer; const { component } = renderer;
const needs_anchor = this.next const needs_anchor = this.next
? !this.next.is_dom_node() : ? !this.next.is_dom_node()
!parent_node || !this.parent.is_dom_node(); : !parent_node || !this.parent.is_dom_node();
const snippet = this.node.expression.manipulate(block); const snippet = this.node.expression.manipulate(block);
@ -206,12 +214,17 @@ export default class EachBlockWrapper extends Wrapper {
block.chunks.init.push(b`@validate_each_argument(${this.vars.each_block_value});`); block.chunks.init.push(b`@validate_each_argument(${this.vars.each_block_value});`);
} }
const initial_anchor_node: Identifier = { type: 'Identifier', name: parent_node ? 'null' : '#anchor' }; const initial_anchor_node: Identifier = {
type: 'Identifier',
name: parent_node ? 'null' : '#anchor'
};
const initial_mount_node: Identifier = parent_node || { type: 'Identifier', name: '#target' }; const initial_mount_node: Identifier = parent_node || { type: 'Identifier', name: '#target' };
const update_anchor_node = needs_anchor const update_anchor_node = needs_anchor
? block.get_unique_name(`${this.var.name}_anchor`) ? block.get_unique_name(`${this.var.name}_anchor`)
: (this.next && this.next.var) || { type: 'Identifier', name: 'null' }; : (this.next && this.next.var) || { type: 'Identifier', name: 'null' };
const update_mount_node: Identifier = this.get_update_mount_node((update_anchor_node as Identifier)); const update_mount_node: Identifier = this.get_update_mount_node(
update_anchor_node as Identifier
);
const args = { const args = {
block, block,
@ -302,7 +315,9 @@ export default class EachBlockWrapper extends Wrapper {
} }
`); `);
const has_transitions = !!(this.else.block.has_intro_method || this.else.block.has_outro_method); const has_transitions = !!(
this.else.block.has_intro_method || this.else.block.has_outro_method
);
const destroy_block_else = this.else.block.has_outro_method const destroy_block_else = this.else.block.has_outro_method
? b` ? b`
@ -362,18 +377,34 @@ export default class EachBlockWrapper extends Wrapper {
this.else.fragment.render(this.else.block, null, x`#nodes` as Identifier); this.else.fragment.render(this.else.block, null, x`#nodes` as Identifier);
} }
this.context_props = this.node.contexts.map(prop => { this.context_props = this.node.contexts.map((prop) => {
if (prop.type === 'DestructuredVariable') { if (prop.type === 'DestructuredVariable') {
const to_ctx = (name: string) => renderer.context_lookup.has(name) ? x`child_ctx[${renderer.context_lookup.get(name).index}]` : { type: 'Identifier', name } as Node; const to_ctx = (name: string) =>
return b`child_ctx[${renderer.context_lookup.get(prop.key.name).index}] = ${prop.default_modifier(prop.modifier(x`list[i]`), to_ctx)};`; renderer.context_lookup.has(name)
? x`child_ctx[${renderer.context_lookup.get(name).index}]`
: ({ type: 'Identifier', name } as Node);
return b`child_ctx[${
renderer.context_lookup.get(prop.key.name).index
}] = ${prop.default_modifier(prop.modifier(x`list[i]`), to_ctx)};`;
} else { } else {
const expression = new Expression(this.renderer.component, this.node, this.node.scope, prop.key); const expression = new Expression(
this.renderer.component,
this.node,
this.node.scope,
prop.key
);
return b`const ${prop.property_name} = ${expression.manipulate(block, 'child_ctx')};`; return b`const ${prop.property_name} = ${expression.manipulate(block, 'child_ctx')};`;
} }
}); });
if (this.node.has_binding) this.context_props.push(b`child_ctx[${renderer.context_lookup.get(this.vars.each_block_value.name).index}] = list;`); if (this.node.has_binding)
if (this.node.has_binding || this.node.has_index_binding || this.node.index) this.context_props.push(b`child_ctx[${renderer.context_lookup.get(this.index_name.name).index}] = i;`); this.context_props.push(
b`child_ctx[${renderer.context_lookup.get(this.vars.each_block_value.name).index}] = list;`
);
if (this.node.has_binding || this.node.has_index_binding || this.node.index)
this.context_props.push(
b`child_ctx[${renderer.context_lookup.get(this.index_name.name).index}] = i;`
);
// TODO which is better — Object.create(array) or array.slice()? // TODO which is better — Object.create(array) or array.slice()?
renderer.blocks.push(b` renderer.blocks.push(b`
@ -405,12 +436,7 @@ export default class EachBlockWrapper extends Wrapper {
update_anchor_node: Identifier; update_anchor_node: Identifier;
update_mount_node: Identifier; update_mount_node: Identifier;
}) { }) {
const { const { create_each_block, iterations, data_length, view_length } = this.vars;
create_each_block,
iterations,
data_length,
view_length
} = this.vars;
const get_key = block.get_unique_name('get_key'); const get_key = block.get_unique_name('get_key');
const lookup = block.get_unique_name(`${this.var.name}_lookup`); const lookup = block.get_unique_name(`${this.var.name}_lookup`);
@ -422,18 +448,16 @@ export default class EachBlockWrapper extends Wrapper {
this.block.first = this.fragment.nodes[0].var; this.block.first = this.fragment.nodes[0].var;
} else { } else {
this.block.first = this.block.get_unique_name('first'); this.block.first = this.block.get_unique_name('first');
this.block.add_element( this.block.add_element(this.block.first, x`@empty()`, parent_nodes && x`@empty()`, null);
this.block.first,
x`@empty()`,
parent_nodes && x`@empty()`,
null
);
} }
block.chunks.init.push(b` block.chunks.init.push(b`
const ${get_key} = #ctx => ${this.node.key.manipulate(block)}; const ${get_key} = #ctx => ${this.node.key.manipulate(block)};
${this.renderer.options.dev && b`@validate_each_keys(#ctx, ${this.vars.each_block_value}, ${this.vars.get_each_context}, ${get_key});`} ${
this.renderer.options.dev &&
b`@validate_each_keys(#ctx, ${this.vars.each_block_value}, ${this.vars.get_each_context}, ${get_key});`
}
for (let #i = 0; #i < ${data_length}; #i += 1) { for (let #i = 0; #i < ${data_length}; #i += 1) {
let child_ctx = ${this.vars.get_each_context}(#ctx, ${this.vars.each_block_value}, #i); let child_ctx = ${this.vars.get_each_context}(#ctx, ${this.vars.each_block_value}, #i);
let key = ${get_key}(child_ctx); let key = ${get_key}(child_ctx);
@ -466,9 +490,9 @@ export default class EachBlockWrapper extends Wrapper {
const dynamic = this.block.has_update_method; const dynamic = this.block.has_update_method;
const destroy = this.node.has_animation const destroy = this.node.has_animation
? (this.block.has_outros ? this.block.has_outros
? '@fix_and_outro_and_destroy_block' ? '@fix_and_outro_and_destroy_block'
: '@fix_and_destroy_block') : '@fix_and_destroy_block'
: this.block.has_outros : this.block.has_outros
? '@outro_and_destroy_block' ? '@outro_and_destroy_block'
: '@destroy_block'; : '@destroy_block';
@ -481,10 +505,23 @@ export default class EachBlockWrapper extends Wrapper {
${this.renderer.options.dev && b`@validate_each_argument(${this.vars.each_block_value});`} ${this.renderer.options.dev && b`@validate_each_argument(${this.vars.each_block_value});`}
${this.block.has_outros && b`@group_outros();`} ${this.block.has_outros && b`@group_outros();`}
${this.node.has_animation && b`for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].r();`} ${
${this.renderer.options.dev && b`@validate_each_keys(#ctx, ${this.vars.each_block_value}, ${this.vars.get_each_context}, ${get_key});`} this.node.has_animation &&
${iterations} = @update_keyed_each(${iterations}, #dirty, ${get_key}, ${dynamic ? 1 : 0}, #ctx, ${this.vars.each_block_value}, ${lookup}, ${update_mount_node}, ${destroy}, ${create_each_block}, ${update_anchor_node}, ${this.vars.get_each_context}); b`for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].r();`
${this.node.has_animation && b`for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].a();`} }
${
this.renderer.options.dev &&
b`@validate_each_keys(#ctx, ${this.vars.each_block_value}, ${this.vars.get_each_context}, ${get_key});`
}
${iterations} = @update_keyed_each(${iterations}, #dirty, ${get_key}, ${dynamic ? 1 : 0}, #ctx, ${
this.vars.each_block_value
}, ${lookup}, ${update_mount_node}, ${destroy}, ${create_each_block}, ${update_anchor_node}, ${
this.vars.get_each_context
});
${
this.node.has_animation &&
b`for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].a();`
}
${this.block.has_outros && b`@check_outros();`} ${this.block.has_outros && b`@check_outros();`}
`); `);
} }
@ -521,13 +558,7 @@ export default class EachBlockWrapper extends Wrapper {
update_anchor_node: Identifier; update_anchor_node: Identifier;
update_mount_node: Identifier; update_mount_node: Identifier;
}) { }) {
const { const { create_each_block, iterations, fixed_length, data_length, view_length } = this.vars;
create_each_block,
iterations,
fixed_length,
data_length,
view_length
} = this.vars;
block.chunks.init.push(b` block.chunks.init.push(b`
let ${iterations} = []; let ${iterations} = [];
@ -614,7 +645,9 @@ export default class EachBlockWrapper extends Wrapper {
`; `;
} else { } else {
remove_old_blocks = b` remove_old_blocks = b`
for (${this.block.has_update_method ? null : x`#i = ${data_length}`}; #i < ${this.block.has_update_method ? view_length : '#old_length'}; #i += 1) { for (${this.block.has_update_method ? null : x`#i = ${data_length}`}; #i < ${
this.block.has_update_method ? view_length : '#old_length'
}; #i += 1) {
${iterations}[#i].d(1); ${iterations}[#i].d(1);
} }
${!fixed_length && b`${view_length} = ${data_length};`} ${!fixed_length && b`${view_length} = ${data_length};`}

@ -72,7 +72,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
} }
if (select && select.select_binding_dependencies) { if (select && select.select_binding_dependencies) {
select.select_binding_dependencies.forEach(prop => { select.select_binding_dependencies.forEach((prop) => {
this.node.dependencies.forEach((dependency: string) => { this.node.dependencies.forEach((dependency: string) => {
this.parent.renderer.component.indirect_dependencies.get(prop).add(dependency); this.parent.renderer.component.indirect_dependencies.get(prop).add(dependency);
}); });
@ -106,7 +106,9 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
} }
// TODO retire this exception in favour of https://github.com/sveltejs/svelte/issues/3750 // TODO retire this exception in favour of https://github.com/sveltejs/svelte/issues/3750
this.is_src = this.name === 'src' && (!this.parent.node.namespace || this.parent.node.namespace === namespaces.html); this.is_src =
this.name === 'src' &&
(!this.parent.node.namespace || this.parent.node.namespace === namespaces.html);
this.should_cache = should_cache(this); this.should_cache = should_cache(this);
} }
@ -123,7 +125,10 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
? '@xlink_attr' ? '@xlink_attr'
: '@attr'; : '@attr';
const is_legacy_input_type = element.renderer.component.compile_options.legacy && name === 'type' && this.parent.node.name === 'input'; const is_legacy_input_type =
element.renderer.component.compile_options.legacy &&
name === 'type' &&
this.parent.node.name === 'input';
const dependencies = this.get_dependencies(); const dependencies = this.get_dependencies();
const value = this.get_value(block); const value = this.get_value(block);
@ -132,9 +137,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
const init = this.get_init(block, value); const init = this.get_init(block, value);
if (is_legacy_input_type) { if (is_legacy_input_type) {
block.chunks.hydrate.push( block.chunks.hydrate.push(b`@set_input_type(${element.var}, ${init});`);
b`@set_input_type(${element.var}, ${init});`
);
updater = b`@set_input_type(${element.var}, ${should_cache ? this.last : value});`; updater = b`@set_input_type(${element.var}, ${should_cache ? this.last : value});`;
} else if (this.is_select_value_attribute) { } else if (this.is_select_value_attribute) {
// annoying special case // annoying special case
@ -155,16 +158,12 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
); );
updater = b`${method}(${element.var}, "${name}", ${should_cache ? this.last : value});`; updater = b`${method}(${element.var}, "${name}", ${should_cache ? this.last : value});`;
} else if (property_name) { } else if (property_name) {
block.chunks.hydrate.push( block.chunks.hydrate.push(b`${element.var}.${property_name} = ${init};`);
b`${element.var}.${property_name} = ${init};`
);
updater = block.renderer.options.dev updater = block.renderer.options.dev
? b`@prop_dev(${element.var}, "${property_name}", ${should_cache ? this.last : value});` ? b`@prop_dev(${element.var}, "${property_name}", ${should_cache ? this.last : value});`
: b`${element.var}.${property_name} = ${should_cache ? this.last : value};`; : b`${element.var}.${property_name} = ${should_cache ? this.last : value};`;
} else { } else {
block.chunks.hydrate.push( block.chunks.hydrate.push(b`${method}(${element.var}, "${name}", ${init});`);
b`${method}(${element.var}, "${name}", ${init});`
);
updater = b`${method}(${element.var}, "${name}", ${should_cache ? this.last : value});`; updater = b`${method}(${element.var}, "${name}", ${should_cache ? this.last : value});`;
} }
@ -179,7 +178,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
} }
if (this.node.name === 'value' && dependencies.length > 0) { if (this.node.name === 'value' && dependencies.length > 0) {
if (this.parent.bindings.some(binding => binding.node.name === 'group')) { if (this.parent.bindings.some((binding) => binding.node.name === 'group')) {
this.parent.dynamic_value_condition = block.get_unique_name('value_has_changed'); this.parent.dynamic_value_condition = block.get_unique_name('value_has_changed');
block.add_variable(this.parent.dynamic_value_condition, x`false`); block.add_variable(this.parent.dynamic_value_condition, x`false`);
updater = b` updater = b`
@ -208,8 +207,13 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
} }
get_init(block: Block, value) { get_init(block: Block, value) {
this.last = this.should_cache && block.get_unique_name( this.last =
`${this.parent.var.name}_${this.name.replace(regex_invalid_variable_identifier_characters, '_')}_value` this.should_cache &&
block.get_unique_name(
`${this.parent.var.name}_${this.name.replace(
regex_invalid_variable_identifier_characters,
'_'
)}_value`
); );
if (this.should_cache) block.add_variable(this.last); if (this.should_cache) block.add_variable(this.last);
@ -233,7 +237,9 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
if (this.is_input_value) { if (this.is_input_value) {
const type = element.node.get_static_attribute_value('type'); const type = element.node.get_static_attribute_value('type');
if (type !== true && !non_textlike_input_types.has(type)) { if (type !== true && !non_textlike_input_types.has(type)) {
condition = x`${condition} && ${element.var}.${property_name} !== ${should_cache ? last : value}`; condition = x`${condition} && ${element.var}.${property_name} !== ${
should_cache ? last : value
}`;
} }
} }
@ -251,7 +257,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
node_dependencies.forEach((prop: string) => { node_dependencies.forEach((prop: string) => {
const indirect_dependencies = this.parent.renderer.component.indirect_dependencies.get(prop); const indirect_dependencies = this.parent.renderer.component.indirect_dependencies.get(prop);
if (indirect_dependencies) { if (indirect_dependencies) {
indirect_dependencies.forEach(indirect_dependency => { indirect_dependencies.forEach((indirect_dependency) => {
dependencies.add(indirect_dependency); dependencies.add(indirect_dependency);
}); });
} }
@ -263,7 +269,8 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
get_metadata() { get_metadata() {
if (this.parent.node.namespace) return null; if (this.parent.node.namespace) return null;
const metadata = attribute_lookup[this.name]; const metadata = attribute_lookup[this.name];
if (metadata && metadata.applies_to && !metadata.applies_to.includes(this.parent.node.name)) return null; if (metadata && metadata.applies_to && !metadata.applies_to.includes(this.parent.node.name))
return null;
return metadata; return metadata;
} }
@ -284,7 +291,8 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
: (this.node.chunks[0] as Expression).manipulate(block); : (this.node.chunks[0] as Expression).manipulate(block);
} }
let value = this.node.name === 'class' let value =
this.node.name === 'class'
? this.get_class_name_text(block) ? this.get_class_name_text(block)
: this.render_chunks(block).reduce((lhs, rhs) => x`${lhs} + ${rhs}`); : this.render_chunks(block).reduce((lhs, rhs) => x`${lhs} + ${rhs}`);
@ -324,17 +332,21 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
const value = this.node.chunks; const value = this.node.chunks;
if (value.length === 0) return '=""'; if (value.length === 0) return '=""';
return `="${value.map(chunk => { return `="${value
.map((chunk) => {
return chunk.type === 'Text' return chunk.type === 'Text'
? chunk.data.replace(regex_double_quotes, '\\"') ? chunk.data.replace(regex_double_quotes, '\\"')
: `\${${chunk.manipulate()}}`; : `\${${chunk.manipulate()}}`;
}).join('')}"`; })
.join('')}"`;
} }
} }
// source: https://html.spec.whatwg.org/multipage/indices.html // source: https://html.spec.whatwg.org/multipage/indices.html
type AttributeMetadata = { property_name?: string, applies_to?: string[] }; type AttributeMetadata = { property_name?: string; applies_to?: string[] };
const attribute_lookup: { [key in BooleanAttributes]: AttributeMetadata } & { [key in string]: AttributeMetadata } = { const attribute_lookup: { [key in BooleanAttributes]: AttributeMetadata } & {
[key in string]: AttributeMetadata;
} = {
allowfullscreen: { property_name: 'allowFullscreen', applies_to: ['iframe'] }, allowfullscreen: { property_name: 'allowFullscreen', applies_to: ['iframe'] },
allowpaymentrequest: { property_name: 'allowPaymentRequest', applies_to: ['iframe'] }, allowpaymentrequest: { property_name: 'allowPaymentRequest', applies_to: ['iframe'] },
async: { applies_to: ['script'] }, async: { applies_to: ['script'] },
@ -387,7 +399,7 @@ const attribute_lookup: { [key in BooleanAttributes]: AttributeMetadata } & { [k
} }
}; };
Object.keys(attribute_lookup).forEach(name => { Object.keys(attribute_lookup).forEach((name) => {
const metadata = attribute_lookup[name]; const metadata = attribute_lookup[name];
if (!metadata.property_name) metadata.property_name = name; if (!metadata.property_name) metadata.property_name = name;
}); });
@ -400,10 +412,12 @@ const regex_contains_checked_or_group = /checked|group/;
function is_indirectly_bound_value(attribute: AttributeWrapper) { function is_indirectly_bound_value(attribute: AttributeWrapper) {
const element = attribute.parent; const element = attribute.parent;
return attribute.name === 'value' && return (
attribute.name === 'value' &&
(element.node.name === 'option' || // TODO check it's actually bound (element.node.name === 'option' || // TODO check it's actually bound
(element.node.name === 'input' && (element.node.name === 'input' &&
element.node.bindings.some( element.node.bindings.some((binding) =>
(binding) => regex_contains_checked_or_group.test(binding.name) regex_contains_checked_or_group.test(binding.name)
))); )))
);
} }

@ -20,7 +20,7 @@ export default class BindingWrapper {
object: string; object: string;
handler: { handler: {
uses_context: boolean; uses_context: boolean;
mutation: (Node | Node[]); mutation: Node | Node[];
contextual_dependencies: Set<string>; contextual_dependencies: Set<string>;
lhs?: Node; lhs?: Node;
}; };
@ -52,7 +52,13 @@ export default class BindingWrapper {
} }
// view to model // view to model
this.handler = get_event_handler(this, parent.renderer, block, this.object, this.node.raw_expression); this.handler = get_event_handler(
this,
parent.renderer,
block,
this.object,
this.node.raw_expression
);
this.snippet = this.node.expression.manipulate(block); this.snippet = this.node.expression.manipulate(block);
@ -67,14 +73,14 @@ export default class BindingWrapper {
this.node.expression.dependencies.forEach((prop: string) => { this.node.expression.dependencies.forEach((prop: string) => {
const indirect_dependencies = this.parent.renderer.component.indirect_dependencies.get(prop); const indirect_dependencies = this.parent.renderer.component.indirect_dependencies.get(prop);
if (indirect_dependencies) { if (indirect_dependencies) {
indirect_dependencies.forEach(indirect_dependency => { indirect_dependencies.forEach((indirect_dependency) => {
dependencies.add(indirect_dependency); dependencies.add(indirect_dependency);
}); });
} }
}); });
if (this.binding_group) { if (this.binding_group) {
this.binding_group.list_dependencies.forEach(dep => dependencies.add(dep)); this.binding_group.list_dependencies.forEach((dep) => dependencies.add(dep));
} }
return dependencies; return dependencies;
@ -93,9 +99,10 @@ export default class BindingWrapper {
const result = new Set(dependencies); const result = new Set(dependencies);
dependencies.forEach((dependency) => { dependencies.forEach((dependency) => {
const indirect_dependencies = this.parent.renderer.component.indirect_dependencies.get(dependency); const indirect_dependencies =
this.parent.renderer.component.indirect_dependencies.get(dependency);
if (indirect_dependencies) { if (indirect_dependencies) {
indirect_dependencies.forEach(indirect_dependency => { indirect_dependencies.forEach((indirect_dependency) => {
result.add(indirect_dependency); result.add(indirect_dependency);
}); });
} }
@ -135,13 +142,9 @@ export default class BindingWrapper {
type === 'search' || type === 'search' ||
type === 'url' type === 'url'
) { ) {
update_conditions.push( update_conditions.push(x`${parent.var}.${this.node.name} !== ${this.snippet}`);
x`${parent.var}.${this.node.name} !== ${this.snippet}`
);
} else if (type === 'number') { } else if (type === 'number') {
update_conditions.push( update_conditions.push(x`@to_number(${parent.var}.${this.node.name}) !== ${this.snippet}`);
x`@to_number(${parent.var}.${this.node.name}) !== ${this.snippet}`
);
} }
} }
@ -151,8 +154,7 @@ export default class BindingWrapper {
// special cases // special cases
switch (this.node.name) { switch (this.node.name) {
case 'group': case 'group': {
{
block.renderer.add_to_context('$$binding_groups'); block.renderer.add_to_context('$$binding_groups');
this.binding_group.add_element(block, this.parent.var); this.binding_group.add_element(block, this.parent.var);
@ -188,8 +190,7 @@ export default class BindingWrapper {
mount_conditions.push(x`!@_isNaN(${this.snippet})`); mount_conditions.push(x`!@_isNaN(${this.snippet})`);
break; break;
case 'paused': case 'paused': {
{
// this is necessary to prevent audio restarting by itself // this is necessary to prevent audio restarting by itself
const last = block.get_unique_name(`${parent.var.name}_is_paused`); const last = block.get_unique_name(`${parent.var.name}_is_paused`);
block.add_variable(last, x`true`); block.add_variable(last, x`true`);
@ -254,16 +255,18 @@ function get_dom_updater(
} }
if (node.name === 'select') { if (node.name === 'select') {
return node.get_static_attribute_value('multiple') === true ? return node.get_static_attribute_value('multiple') === true
b`@select_options(${element.var}, ${binding.snippet})` : ? b`@select_options(${element.var}, ${binding.snippet})`
mounting ? b`@select_option(${element.var}, ${binding.snippet}, true)` : : mounting
b`@select_option(${element.var}, ${binding.snippet})`; ? b`@select_option(${element.var}, ${binding.snippet}, true)`
: b`@select_option(${element.var}, ${binding.snippet})`;
} }
if (binding.node.name === 'group') { if (binding.node.name === 'group') {
const type = node.get_static_attribute_value('type'); const type = node.get_static_attribute_value('type');
const condition = type === 'checkbox' const condition =
type === 'checkbox'
? x`~(${binding.snippet} || []).indexOf(${element.var}.__value)` ? x`~(${binding.snippet} || []).indexOf(${element.var}.__value)`
: x`${element.var}.__value === ${binding.snippet}`; : x`${element.var}.__value === ${binding.snippet}`;
@ -335,7 +338,7 @@ function get_binding_group(renderer: Renderer, binding: BindingWrapper, block: B
*/ */
const elements = new Map<Block, any>(); const elements = new Map<Block, any>();
contexts.forEach(context => { contexts.forEach((context) => {
renderer.add_to_context(context, true); renderer.add_to_context(context, true);
}); });
@ -344,7 +347,7 @@ function get_binding_group(renderer: Renderer, binding: BindingWrapper, block: B
let obj = x`$$binding_groups[${index}]`; let obj = x`$$binding_groups[${index}]`;
if (contexts.length > 0) { if (contexts.length > 0) {
contexts.forEach(secondary_index => { contexts.forEach((secondary_index) => {
obj = x`${obj}[${secondary_index}]`; obj = x`${obj}[${secondary_index}]`;
}); });
} }
@ -364,24 +367,25 @@ function get_binding_group(renderer: Renderer, binding: BindingWrapper, block: B
const binding_group = block.renderer.reference('$$binding_groups'); const binding_group = block.renderer.reference('$$binding_groups');
block.add_variable(local_name); block.add_variable(local_name);
if (contexts.length > 0) { if (contexts.length > 0) {
const indexes = { type: 'ArrayExpression', elements: contexts.map(name => block.renderer.reference(name)) }; const indexes = {
type: 'ArrayExpression',
elements: contexts.map((name) => block.renderer.reference(name))
};
block.chunks.init.push( block.chunks.init.push(
b`${local_name} = @init_binding_group_dynamic(${binding_group}[${index}], ${indexes})` b`${local_name} = @init_binding_group_dynamic(${binding_group}[${index}], ${indexes})`
); );
block.chunks.update.push( block.chunks.update.push(
b`if (${block.renderer.dirty(Array.from(list_dependencies))}) ${local_name}.u(${indexes})` b`if (${block.renderer.dirty(
Array.from(list_dependencies)
)}) ${local_name}.u(${indexes})`
); );
} else { } else {
block.chunks.init.push( block.chunks.init.push(
b`${local_name} = @init_binding_group(${binding_group}[${index}])` b`${local_name} = @init_binding_group(${binding_group}[${index}])`
); );
} }
block.chunks.hydrate.push( block.chunks.hydrate.push(b`${local_name}.p(${elements.get(block)})`);
b`${local_name}.p(${elements.get(block)})` block.chunks.destroy.push(b`${local_name}.r()`);
);
block.chunks.destroy.push(
b`${local_name}.r()`
);
} }
}); });
} }
@ -401,7 +405,7 @@ function get_event_handler(
lhs: Node lhs: Node
): { ): {
uses_context: boolean; uses_context: boolean;
mutation: (Node | Node[]); mutation: Node | Node[];
contextual_dependencies: Set<string>; contextual_dependencies: Set<string>;
lhs?: Node; lhs?: Node;
} { } {
@ -463,9 +467,9 @@ function get_value_from_dom(
// <select bind:value='selected> // <select bind:value='selected>
if (node.name === 'select') { if (node.name === 'select') {
return node.get_static_attribute_value('multiple') === true ? return node.get_static_attribute_value('multiple') === true
x`@select_multiple_value(this)` : ? x`@select_multiple_value(this)`
x`@select_value(this)`; : x`@select_value(this)`;
} }
const type = node.get_static_attribute_value('type'); const type = node.get_static_attribute_value('type');
@ -486,7 +490,7 @@ function get_value_from_dom(
return x`@to_number(this.${name})`; return x`@to_number(this.${name})`;
} }
if ((name === 'buffered' || name === 'seekable' || name === 'played')) { if (name === 'buffered' || name === 'seekable' || name === 'played') {
return x`@time_ranges_to_array(this.${name})`; return x`@time_ranges_to_array(this.${name})`;
} }

@ -27,7 +27,9 @@ export default class EventHandlerWrapper {
} }
get_snippet(block: Block) { get_snippet(block: Block) {
const snippet = this.node.expression ? this.node.expression.manipulate(block) : block.renderer.reference(this.node.handler_name); const snippet = this.node.expression
? this.node.expression.manipulate(block)
: block.renderer.reference(this.node.handler_name);
if (this.node.reassigned) { if (this.node.reassigned) {
block.maintain_context = true; block.maintain_context = true;
@ -41,22 +43,23 @@ export default class EventHandlerWrapper {
if (this.node.modifiers.has('preventDefault')) snippet = x`@prevent_default(${snippet})`; 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('stopPropagation')) snippet = x`@stop_propagation(${snippet})`;
if (this.node.modifiers.has('stopImmediatePropagation')) snippet = x`@stop_immediate_propagation(${snippet})`; if (this.node.modifiers.has('stopImmediatePropagation'))
snippet = x`@stop_immediate_propagation(${snippet})`;
if (this.node.modifiers.has('self')) snippet = x`@self(${snippet})`; if (this.node.modifiers.has('self')) snippet = x`@self(${snippet})`;
if (this.node.modifiers.has('trusted')) snippet = x`@trusted(${snippet})`; if (this.node.modifiers.has('trusted')) snippet = x`@trusted(${snippet})`;
const args = []; const args = [];
const opts = ['nonpassive', 'passive', 'once', 'capture'].filter(mod => this.node.modifiers.has(mod)); const opts = ['nonpassive', 'passive', 'once', 'capture'].filter((mod) =>
this.node.modifiers.has(mod)
);
if (opts.length) { if (opts.length) {
if (opts.length === 1 && opts[0] === 'capture') { if (opts.length === 1 && opts[0] === 'capture') {
args.push(TRUE); args.push(TRUE);
} else { } else {
args.push(x`{ ${ opts.map(opt => args.push(
opt === 'nonpassive' x`{ ${opts.map((opt) => (opt === 'nonpassive' ? p`passive: false` : p`${opt}: true`))} }`
? p`passive: false` );
: p`${opt}: true`
) } }`);
} }
} else if (block.renderer.options.dev) { } else if (block.renderer.options.dev) {
args.push(FALSE); args.push(FALSE);
@ -68,8 +71,6 @@ export default class EventHandlerWrapper {
args.push(this.node.modifiers.has('stopImmediatePropagation') ? TRUE : FALSE); args.push(this.node.modifiers.has('stopImmediatePropagation') ? TRUE : FALSE);
} }
block.event_listeners.push( block.event_listeners.push(x`@listen(${target}, "${this.node.name}", ${snippet}, ${args})`);
x`@listen(${target}, "${this.node.name}", ${snippet}, ${args})`
);
} }
} }

@ -29,7 +29,7 @@ export default class StyleAttributeWrapper extends AttributeWrapper {
const prop_dependencies: Set<string> = new Set(); const prop_dependencies: Set<string> = new Set();
value = prop.value value = prop.value
.map(chunk => { .map((chunk) => {
if (chunk.type === 'Text') { if (chunk.type === 'Text') {
return string_literal(chunk.data); return string_literal(chunk.data);
} else { } else {

@ -1,8 +1,11 @@
const svg_attributes = 'accent-height accumulate additive alignment-baseline allowReorder alphabetic amplitude arabic-form ascent attributeName attributeType autoReverse azimuth baseFrequency baseline-shift baseProfile bbox begin bias by calcMode cap-height class clip clipPathUnits clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering contentScriptType contentStyleType cursor cx cy d decelerate descent diffuseConstant direction display divisor dominant-baseline dur dx dy edgeMode elevation enable-background end exponent externalResourcesRequired fill fill-opacity fill-rule filter filterRes filterUnits flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight format from fr fx fy g1 g2 glyph-name glyph-orientation-horizontal glyph-orientation-vertical glyphRef gradientTransform gradientUnits hanging height href horiz-adv-x horiz-origin-x id ideographic image-rendering in in2 intercept k k1 k2 k3 k4 kernelMatrix kernelUnitLength kerning keyPoints keySplines keyTimes lang lengthAdjust letter-spacing lighting-color limitingConeAngle local marker-end marker-mid marker-start markerHeight markerUnits markerWidth mask maskContentUnits maskUnits mathematical max media method min mode name numOctaves offset onabort onactivate onbegin onclick onend onerror onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup onrepeat onresize onscroll onunload opacity operator order orient orientation origin overflow overline-position overline-thickness panose-1 paint-order pathLength patternContentUnits patternTransform patternUnits pointer-events points pointsAtX pointsAtY pointsAtZ preserveAlpha preserveAspectRatio primitiveUnits r radius refX refY rendering-intent repeatCount repeatDur requiredExtensions requiredFeatures restart result rotate rx ry scale seed shape-rendering slope spacing specularConstant specularExponent speed spreadMethod startOffset stdDeviation stemh stemv stitchTiles stop-color stop-opacity strikethrough-position strikethrough-thickness string stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style surfaceScale systemLanguage tabindex tableValues target targetX targetY text-anchor text-decoration text-rendering textLength to transform type u1 u2 underline-position underline-thickness unicode unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical values version vert-adv-y vert-origin-x vert-origin-y viewBox viewTarget visibility width widths word-spacing writing-mode x x-height x1 x2 xChannelSelector xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space y y1 y2 yChannelSelector z zoomAndPan'.split(' '); const svg_attributes =
'accent-height accumulate additive alignment-baseline allowReorder alphabetic amplitude arabic-form ascent attributeName attributeType autoReverse azimuth baseFrequency baseline-shift baseProfile bbox begin bias by calcMode cap-height class clip clipPathUnits clip-path clip-rule color color-interpolation color-interpolation-filters color-profile color-rendering contentScriptType contentStyleType cursor cx cy d decelerate descent diffuseConstant direction display divisor dominant-baseline dur dx dy edgeMode elevation enable-background end exponent externalResourcesRequired fill fill-opacity fill-rule filter filterRes filterUnits flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight format from fr fx fy g1 g2 glyph-name glyph-orientation-horizontal glyph-orientation-vertical glyphRef gradientTransform gradientUnits hanging height href horiz-adv-x horiz-origin-x id ideographic image-rendering in in2 intercept k k1 k2 k3 k4 kernelMatrix kernelUnitLength kerning keyPoints keySplines keyTimes lang lengthAdjust letter-spacing lighting-color limitingConeAngle local marker-end marker-mid marker-start markerHeight markerUnits markerWidth mask maskContentUnits maskUnits mathematical max media method min mode name numOctaves offset onabort onactivate onbegin onclick onend onerror onfocusin onfocusout onload onmousedown onmousemove onmouseout onmouseover onmouseup onrepeat onresize onscroll onunload opacity operator order orient orientation origin overflow overline-position overline-thickness panose-1 paint-order pathLength patternContentUnits patternTransform patternUnits pointer-events points pointsAtX pointsAtY pointsAtZ preserveAlpha preserveAspectRatio primitiveUnits r radius refX refY rendering-intent repeatCount repeatDur requiredExtensions requiredFeatures restart result rotate rx ry scale seed shape-rendering slope spacing specularConstant specularExponent speed spreadMethod startOffset stdDeviation stemh stemv stitchTiles stop-color stop-opacity strikethrough-position strikethrough-thickness string stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width style surfaceScale systemLanguage tabindex tableValues target targetX targetY text-anchor text-decoration text-rendering textLength to transform type u1 u2 underline-position underline-thickness unicode unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical values version vert-adv-y vert-origin-x vert-origin-y viewBox viewTarget visibility width widths word-spacing writing-mode x x-height x1 x2 xChannelSelector xlink:actuate xlink:arcrole xlink:href xlink:role xlink:show xlink:title xlink:type xml:base xml:lang xml:space y y1 y2 yChannelSelector z zoomAndPan'.split(
' '
);
const svg_attribute_lookup = new Map(); const svg_attribute_lookup = new Map();
svg_attributes.forEach(name => { svg_attributes.forEach((name) => {
svg_attribute_lookup.set(name.toLowerCase(), name); svg_attribute_lookup.set(name.toLowerCase(), name);
}); });

@ -12,7 +12,15 @@ import { namespaces } from '../../../../utils/namespaces';
import AttributeWrapper from './Attribute'; import AttributeWrapper from './Attribute';
import StyleAttributeWrapper from './StyleAttribute'; import StyleAttributeWrapper from './StyleAttribute';
import SpreadAttributeWrapper from './SpreadAttribute'; import SpreadAttributeWrapper from './SpreadAttribute';
import { regex_dimensions, regex_starts_with_newline, regex_backslashes, regex_border_box_size, regex_content_box_size, regex_device_pixel_content_box_size, regex_content_rect } from '../../../../utils/patterns'; import {
regex_dimensions,
regex_starts_with_newline,
regex_backslashes,
regex_border_box_size,
regex_content_box_size,
regex_device_pixel_content_box_size,
regex_content_rect
} from '../../../../utils/patterns';
import Binding from './Binding'; import Binding from './Binding';
import add_to_set from '../../../utils/add_to_set'; import add_to_set from '../../../utils/add_to_set';
import { add_event_handler } from '../shared/add_event_handlers'; import { add_event_handler } from '../shared/add_event_handlers';
@ -44,21 +52,24 @@ const events = [
event_names: ['input'], event_names: ['input'],
filter: (node: Element, _name: string) => filter: (node: Element, _name: string) =>
node.name === 'textarea' || node.name === 'textarea' ||
node.name === 'input' && (node.name === 'input' &&
!regex_contains_radio_or_checkbox_or_range_or_file.test(node.get_static_attribute_value('type') as string) !regex_contains_radio_or_checkbox_or_range_or_file.test(
node.get_static_attribute_value('type') as string
))
}, },
{ {
event_names: ['input'], event_names: ['input'],
filter: (node: Element, name: string) => filter: (node: Element, name: string) =>
is_name_contenteditable(name) && is_name_contenteditable(name) && has_contenteditable_attr(node)
has_contenteditable_attr(node)
}, },
{ {
event_names: ['change'], event_names: ['change'],
filter: (node: Element, _name: string) => filter: (node: Element, _name: string) =>
node.name === 'select' || node.name === 'select' ||
node.name === 'input' && (node.name === 'input' &&
regex_contains_radio_or_checkbox_or_file.test(node.get_static_attribute_value('type') as string) regex_contains_radio_or_checkbox_or_file.test(
node.get_static_attribute_value('type') as string
))
}, },
{ {
event_names: ['change', 'input'], event_names: ['change', 'input'],
@ -68,8 +79,7 @@ const events = [
// resize events // resize events
{ {
event_names: ['elementresize'], event_names: ['elementresize'],
filter: (_node: Element, name: string) => filter: (_node: Element, name: string) => regex_dimensions.test(name)
regex_dimensions.test(name)
}, },
{ {
event_names: ['elementresizecontentbox'], event_names: ['elementresizecontentbox'],
@ -79,89 +89,76 @@ const events = [
{ {
event_names: ['elementresizeborderbox'], event_names: ['elementresizeborderbox'],
filter: (_node: Element, name: string) => filter: (_node: Element, name: string) => regex_border_box_size.test(name)
regex_border_box_size.test(name)
}, },
{ {
event_names: ['elementresizedevicepixelcontentbox'], event_names: ['elementresizedevicepixelcontentbox'],
filter: (_node: Element, name: string) => filter: (_node: Element, name: string) => regex_device_pixel_content_box_size.test(name)
regex_device_pixel_content_box_size.test(name)
}, },
// media events // media events
{ {
event_names: ['timeupdate'], event_names: ['timeupdate'],
filter: (node: Element, name: string) => filter: (node: Element, name: string) =>
node.is_media_node() && node.is_media_node() && (name === 'currentTime' || name === 'played' || name === 'ended')
(name === 'currentTime' || name === 'played' || name === 'ended')
}, },
{ {
event_names: ['durationchange'], event_names: ['durationchange'],
filter: (node: Element, name: string) => filter: (node: Element, name: string) => node.is_media_node() && name === 'duration'
node.is_media_node() &&
name === 'duration'
}, },
{ {
event_names: ['play', 'pause'], event_names: ['play', 'pause'],
filter: (node: Element, name: string) => filter: (node: Element, name: string) => node.is_media_node() && name === 'paused'
node.is_media_node() &&
name === 'paused'
}, },
{ {
event_names: ['progress'], event_names: ['progress'],
filter: (node: Element, name: string) => filter: (node: Element, name: string) => node.is_media_node() && name === 'buffered'
node.is_media_node() &&
name === 'buffered'
}, },
{ {
event_names: ['loadedmetadata'], event_names: ['loadedmetadata'],
filter: (node: Element, name: string) => filter: (node: Element, name: string) =>
node.is_media_node() && node.is_media_node() && (name === 'buffered' || name === 'seekable')
(name === 'buffered' || name === 'seekable')
}, },
{ {
event_names: ['volumechange'], event_names: ['volumechange'],
filter: (node: Element, name: string) => filter: (node: Element, name: string) =>
node.is_media_node() && node.is_media_node() && (name === 'volume' || name === 'muted')
(name === 'volume' || name === 'muted')
}, },
{ {
event_names: ['ratechange'], event_names: ['ratechange'],
filter: (node: Element, name: string) => filter: (node: Element, name: string) => node.is_media_node() && name === 'playbackRate'
node.is_media_node() &&
name === 'playbackRate'
}, },
{ {
event_names: ['seeking', 'seeked'], event_names: ['seeking', 'seeked'],
filter: (node: Element, name: string) => filter: (node: Element, name: string) => node.is_media_node() && name === 'seeking'
node.is_media_node() &&
(name === 'seeking')
}, },
{ {
event_names: ['ended'], event_names: ['ended'],
filter: (node: Element, name: string) => filter: (node: Element, name: string) => node.is_media_node() && name === 'ended'
node.is_media_node() &&
name === 'ended'
}, },
{ {
event_names: ['resize'], event_names: ['resize'],
filter: (node: Element, name: string) => filter: (node: Element, name: string) =>
node.is_media_node() && node.is_media_node() && (name === 'videoHeight' || name === 'videoWidth')
(name === 'videoHeight' || name === 'videoWidth')
}, },
{ {
// from https://html.spec.whatwg.org/multipage/media.html#ready-states // from https://html.spec.whatwg.org/multipage/media.html#ready-states
// and https://html.spec.whatwg.org/multipage/media.html#loading-the-media-resource // and https://html.spec.whatwg.org/multipage/media.html#loading-the-media-resource
event_names: ['loadedmetadata', 'loadeddata', 'canplay', 'canplaythrough', 'playing', 'waiting', 'emptied'], event_names: [
filter: (node: Element, name: string) => 'loadedmetadata',
node.is_media_node() && 'loadeddata',
name === 'readyState' 'canplay',
'canplaythrough',
'playing',
'waiting',
'emptied'
],
filter: (node: Element, name: string) => node.is_media_node() && name === 'readyState'
}, },
// details event // details event
{ {
event_names: ['toggle'], event_names: ['toggle'],
filter: (node: Element, _name: string) => filter: (node: Element, _name: string) => node.name === 'details'
node.name === 'details'
}, },
{ {
event_names: ['load'], event_names: ['load'],
@ -240,14 +237,14 @@ export default class ElementWrapper extends Wrapper {
this.dynamic_style_dependencies = new Set(); this.dynamic_style_dependencies = new Set();
if (this.node.children.length) { if (this.node.children.length) {
this.node.lets.forEach(l => { this.node.lets.forEach((l) => {
extract_names(l.value || l.name).forEach(name => { extract_names(l.value || l.name).forEach((name) => {
renderer.add_to_context(name, true); renderer.add_to_context(name, true);
}); });
}); });
} }
this.attributes = this.node.attributes.map(attribute => { this.attributes = this.node.attributes.map((attribute) => {
if (attribute.name === 'style') { if (attribute.name === 'style') {
return new StyleAttributeWrapper(this, block, attribute); return new StyleAttributeWrapper(this, block, attribute);
} }
@ -256,14 +253,18 @@ export default class ElementWrapper extends Wrapper {
} }
return new AttributeWrapper(this, block, attribute); return new AttributeWrapper(this, block, attribute);
}); });
this.has_dynamic_attribute = !!this.attributes.find(attr => attr.node.get_dependencies().length > 0); this.has_dynamic_attribute = !!this.attributes.find(
(attr) => attr.node.get_dependencies().length > 0
);
// ordinarily, there'll only be one... but we need to handle // ordinarily, there'll only be one... but we need to handle
// the rare case where an element can have multiple bindings, // the rare case where an element can have multiple bindings,
// e.g. <audio bind:paused bind:currentTime> // e.g. <audio bind:paused bind:currentTime>
this.bindings = this.node.bindings.map(binding => new Binding(block, binding, this)); this.bindings = this.node.bindings.map((binding) => new Binding(block, binding, this));
this.event_handlers = this.node.handlers.map(event_handler => new EventHandler(event_handler, this)); this.event_handlers = this.node.handlers.map(
(event_handler) => new EventHandler(event_handler, this)
);
if (node.intro || node.outro) { if (node.intro || node.outro) {
if (node.intro) block.add_intro(node.intro.is_local); if (node.intro) block.add_intro(node.intro.is_local);
@ -277,19 +278,28 @@ export default class ElementWrapper extends Wrapper {
block.add_dependencies(node.tag_expr.dependencies); block.add_dependencies(node.tag_expr.dependencies);
// add directive and handler dependencies // add directive and handler dependencies
[node.animation, node.outro, ...node.actions, ...node.classes, ...node.styles].forEach(directive => { [node.animation, node.outro, ...node.actions, ...node.classes, ...node.styles].forEach(
(directive) => {
if (directive && directive.expression) { if (directive && directive.expression) {
block.add_dependencies(directive.expression.dependencies); block.add_dependencies(directive.expression.dependencies);
} }
}); }
);
node.handlers.forEach(handler => { node.handlers.forEach((handler) => {
if (handler.expression) { if (handler.expression) {
block.add_dependencies(handler.expression.dependencies); block.add_dependencies(handler.expression.dependencies);
} }
}); });
this.fragment = new FragmentWrapper(renderer, block, node.children, this, strip_whitespace, next_sibling); this.fragment = new FragmentWrapper(
renderer,
block,
node.children,
this,
strip_whitespace,
next_sibling
);
this.element_data_name = block.get_unique_name(`${this.var.name}_data`); this.element_data_name = block.get_unique_name(`${this.var.name}_data`);
} }
@ -306,7 +316,7 @@ export default class ElementWrapper extends Wrapper {
this.child_dynamic_element.render( this.child_dynamic_element.render(
this.child_dynamic_element_block, this.child_dynamic_element_block,
null, null,
(x`#nodes` as unknown) as Identifier x`#nodes` as unknown as Identifier
); );
const is_tag_dynamic = this.node.tag_expr.dynamic_dependencies().length > 0; const is_tag_dynamic = this.node.tag_expr.dynamic_dependencies().length > 0;
@ -314,7 +324,11 @@ export default class ElementWrapper extends Wrapper {
block.chunks.init.push(b` block.chunks.init.push(b`
${this.renderer.options.dev && b`@validate_dynamic_element(${tag});`} ${this.renderer.options.dev && b`@validate_dynamic_element(${tag});`}
${this.renderer.options.dev && this.node.children.length > 0 && b`@validate_void_dynamic_element(${tag});`} ${
this.renderer.options.dev &&
this.node.children.length > 0 &&
b`@validate_void_dynamic_element(${tag});`
}
let ${this.var} = ${tag} && ${this.child_dynamic_element_block.name}(#ctx); let ${this.var} = ${tag} && ${this.child_dynamic_element_block.name}(#ctx);
`); `);
@ -337,7 +351,9 @@ export default class ElementWrapper extends Wrapper {
block.add_variable(previous_tag, tag); block.add_variable(previous_tag, tag);
const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes); const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes);
const has_transitions = !!(this.node.intro || this.node.outro); const has_transitions = !!(this.node.intro || this.node.outro);
const not_equal = this.renderer.component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`; const not_equal = this.renderer.component.component_options.immutable
? x`@not_equal`
: x`@safe_not_equal`;
const tag_will_be_removed = block.get_unique_name('tag_will_be_removed'); const tag_will_be_removed = block.get_unique_name('tag_will_be_removed');
if (has_transitions) { if (has_transitions) {
@ -355,24 +371,35 @@ export default class ElementWrapper extends Wrapper {
} else if (${not_equal}(${previous_tag}, ${tag})) { } else if (${not_equal}(${previous_tag}, ${tag})) {
${this.var}.d(1); ${this.var}.d(1);
${this.renderer.options.dev && b`@validate_dynamic_element(${tag});`} ${this.renderer.options.dev && b`@validate_dynamic_element(${tag});`}
${this.renderer.options.dev && this.node.children.length > 0 && b`@validate_void_dynamic_element(${tag});`} ${
this.renderer.options.dev &&
this.node.children.length > 0 &&
b`@validate_void_dynamic_element(${tag});`
}
${this.var} = ${this.child_dynamic_element_block.name}(#ctx); ${this.var} = ${this.child_dynamic_element_block.name}(#ctx);
${previous_tag} = ${tag}; ${previous_tag} = ${tag};
${this.var}.c(); ${this.var}.c();
${has_transitions && b`if (${tag_will_be_removed}) { ${
has_transitions &&
b`if (${tag_will_be_removed}) {
${tag_will_be_removed} = false; ${tag_will_be_removed} = false;
@transition_in(${this.var}) @transition_in(${this.var})
}`} }`
}
${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor}); ${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor});
} else { } else {
${has_transitions && b`if (${tag_will_be_removed}) { ${
has_transitions &&
b`if (${tag_will_be_removed}) {
${tag_will_be_removed} = false; ${tag_will_be_removed} = false;
@transition_in(${this.var}) @transition_in(${this.var})
}`} }`
}
${this.var}.p(#ctx, #dirty); ${this.var}.p(#ctx, #dirty);
} }
} else if (${previous_tag}) { } else if (${previous_tag}) {
${has_transitions ${
has_transitions
? b` ? b`
${tag_will_be_removed} = true; ${tag_will_be_removed} = true;
@group_outros(); @group_outros();
@ -426,7 +453,6 @@ export default class ElementWrapper extends Wrapper {
} }
render_element(block: Block, parent_node: Identifier, parent_nodes: Identifier) { render_element(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
const { renderer } = this; const { renderer } = this;
const hydratable = renderer.options.hydratable; const hydratable = renderer.options.hydratable;
@ -438,9 +464,7 @@ export default class ElementWrapper extends Wrapper {
block.add_variable(node); block.add_variable(node);
const render_statement = this.get_render_statement(block); const render_statement = this.get_render_statement(block);
block.chunks.create.push( block.chunks.create.push(b`${node} = ${render_statement};`);
b`${node} = ${render_statement};`
);
const { can_use_textcontent, can_optimise_to_html_string } = this.node; const { can_use_textcontent, can_optimise_to_html_string } = this.node;
@ -456,9 +480,7 @@ export default class ElementWrapper extends Wrapper {
`); `);
} }
} else { } else {
block.chunks.claim.push( block.chunks.claim.push(b`${node} = ${render_statement};`);
b`${node} = ${render_statement};`
);
} }
} }
@ -499,7 +521,9 @@ export default class ElementWrapper extends Wrapper {
block.chunks.create.push(b`${node}.textContent = ${text};`); block.chunks.create.push(b`${node}.textContent = ${text};`);
if (hydratable) { if (hydratable) {
block.chunks.claim.push(b`if (@get_svelte_dataset(${node}) !== "${this.node.hash()}") ${node}.textContent = ${text};`); block.chunks.claim.push(
b`if (@get_svelte_dataset(${node}) !== "${this.node.hash()}") ${node}.textContent = ${text};`
);
} }
} else { } else {
const state = { const state = {
@ -516,7 +540,13 @@ export default class ElementWrapper extends Wrapper {
}; };
const can_use_raw_text = !this.node.can_use_innerhtml && can_use_textcontent; const can_use_raw_text = !this.node.can_use_innerhtml && can_use_textcontent;
to_html((this.fragment.nodes as unknown as Array<ElementWrapper | CommentWrapper | TextWrapper>), block, literal, state, can_use_raw_text); to_html(
this.fragment.nodes as unknown as Array<ElementWrapper | CommentWrapper | TextWrapper>,
block,
literal,
state,
can_use_raw_text
);
literal.quasis.push(state.quasi as any); literal.quasis.push(state.quasi as any);
if (hydratable) { if (hydratable) {
@ -528,25 +558,23 @@ export default class ElementWrapper extends Wrapper {
block.chunks.create.push(b`${node}.${property} = ${literal};`); block.chunks.create.push(b`${node}.${property} = ${literal};`);
if (hydratable) { if (hydratable) {
block.chunks.claim.push(b`if (@get_svelte_dataset(${node}) !== "${this.node.hash()}") ${node}.${property} = ${literal};`); block.chunks.claim.push(
b`if (@get_svelte_dataset(${node}) !== "${this.node.hash()}") ${node}.${property} = ${literal};`
);
} }
} }
} else { } else {
this.fragment.nodes.forEach((child: Wrapper) => { this.fragment.nodes.forEach((child: Wrapper) => {
child.render( child.render(block, this.node.name === 'template' ? x`${node}.content` : node, nodes, {
block, element_data_name: this.element_data_name
this.node.name === 'template' ? x`${node}.content` : node, });
nodes,
{ element_data_name: this.element_data_name }
);
}); });
} }
const event_handler_or_binding_uses_context = ( const event_handler_or_binding_uses_context =
this.bindings.some(binding => binding.handler.uses_context) || this.bindings.some((binding) => binding.handler.uses_context) ||
this.node.handlers.some(handler => handler.uses_context) || this.node.handlers.some((handler) => handler.uses_context) ||
this.node.actions.some(action => action.uses_context) this.node.actions.some((action) => action.uses_context);
);
if (event_handler_or_binding_uses_context) { if (event_handler_or_binding_uses_context) {
block.maintain_context = true; block.maintain_context = true;
@ -573,7 +601,9 @@ export default class ElementWrapper extends Wrapper {
if (renderer.options.dev) { if (renderer.options.dev) {
const loc = renderer.locate(this.node.start); const loc = renderer.locate(this.node.start);
block.chunks.hydrate.push( block.chunks.hydrate.push(
b`@add_location(${this.var}, ${renderer.file_var}, ${loc.line - 1}, ${loc.column}, ${this.node.start});` b`@add_location(${this.var}, ${renderer.file_var}, ${loc.line - 1}, ${loc.column}, ${
this.node.start
});`
); );
} }
@ -592,9 +622,11 @@ export default class ElementWrapper extends Wrapper {
return x`@_document.createElementNS("${namespace}", "${name}")`; return x`@_document.createElementNS("${namespace}", "${name}")`;
} }
const is: AttributeWrapper = this.attributes.find(attr => attr.node.name === 'is') as any; const is: AttributeWrapper = this.attributes.find((attr) => attr.node.name === 'is') as any;
if (is) { if (is) {
return x`@element_is("${name}", ${is.render_chunks(block).reduce((lhs, rhs) => x`${lhs} + ${rhs}`)})`; return x`@element_is("${name}", ${is
.render_chunks(block)
.reduce((lhs, rhs) => x`${lhs} + ${rhs}`)})`;
} }
return x`@element(${reference})`; return x`@element(${reference})`;
@ -610,7 +642,7 @@ export default class ElementWrapper extends Wrapper {
if (this.node.namespace) { if (this.node.namespace) {
reference = `"${this.node.tag_expr.node.value}"`; reference = `"${this.node.tag_expr.node.value}"`;
} else { } else {
reference = `"${(this.node.tag_expr.node.value as String || '').toUpperCase()}"`; reference = `"${((this.node.tag_expr.node.value as String) || '').toUpperCase()}"`;
} }
} else if (this.node.namespace) { } else if (this.node.namespace) {
reference = x`${this.node.tag_expr.manipulate(block)}`; reference = x`${this.node.tag_expr.manipulate(block)}`;
@ -633,15 +665,15 @@ export default class ElementWrapper extends Wrapper {
type OrderedAttribute = EventHandler | BindingGroup | Binding | Action; type OrderedAttribute = EventHandler | BindingGroup | Binding | Action;
const binding_groups = events const binding_groups = events
.map(event => ({ .map((event) => ({
events: event.event_names, events: event.event_names,
bindings: this.bindings bindings: this.bindings
.filter(binding => binding.node.name !== 'this') .filter((binding) => binding.node.name !== 'this')
.filter(binding => event.filter(this.node, binding.node.name)) .filter((binding) => event.filter(this.node, binding.node.name))
})) }))
.filter(group => group.bindings.length); .filter((group) => group.bindings.length);
const this_binding = this.bindings.find(b => b.node.name === 'this'); const this_binding = this.bindings.find((b) => b.node.name === 'this');
function getOrder(item: OrderedAttribute) { function getOrder(item: OrderedAttribute) {
if (item instanceof EventHandler) { if (item instanceof EventHandler) {
@ -655,15 +687,17 @@ export default class ElementWrapper extends Wrapper {
} }
} }
([ (
[
...binding_groups, ...binding_groups,
...this.event_handlers, ...this.event_handlers,
this_binding, this_binding,
...this.node.actions ...this.node.actions
] as OrderedAttribute[]) ] as OrderedAttribute[]
)
.filter(Boolean) .filter(Boolean)
.sort((a, b) => getOrder(a) - getOrder(b)) .sort((a, b) => getOrder(a) - getOrder(b))
.forEach(item => { .forEach((item) => {
if (item instanceof EventHandler) { if (item instanceof EventHandler) {
add_event_handler(block, this.var, item); add_event_handler(block, this.var, item);
} else if (item instanceof Binding) { } else if (item instanceof Binding) {
@ -683,22 +717,24 @@ export default class ElementWrapper extends Wrapper {
renderer.component.has_reactive_assignments = true; renderer.component.has_reactive_assignments = true;
const lock = binding_group.bindings.some(binding => binding.needs_lock) ? const lock = binding_group.bindings.some((binding) => binding.needs_lock)
block.get_unique_name(`${this.var.name}_updating`) : ? block.get_unique_name(`${this.var.name}_updating`)
null; : null;
if (lock) block.add_variable(lock, x`false`); if (lock) block.add_variable(lock, x`false`);
const handler = renderer.component.get_unique_name(`${this.var.name}_${binding_group.events.join('_')}_handler`); const handler = renderer.component.get_unique_name(
`${this.var.name}_${binding_group.events.join('_')}_handler`
);
renderer.add_to_context(handler.name); renderer.add_to_context(handler.name);
// TODO figure out how to handle locks // TODO figure out how to handle locks
const needs_lock = binding_group.bindings.some(binding => binding.needs_lock); const needs_lock = binding_group.bindings.some((binding) => binding.needs_lock);
const dependencies: Set<string> = new Set(); const dependencies: Set<string> = new Set();
const contextual_dependencies: Set<string> = new Set(); const contextual_dependencies: Set<string> = new Set();
binding_group.bindings.forEach(binding => { binding_group.bindings.forEach((binding) => {
// TODO this is a mess // TODO this is a mess
add_to_set(dependencies, binding.get_update_dependencies()); add_to_set(dependencies, binding.get_update_dependencies());
add_to_set(contextual_dependencies, binding.handler.contextual_dependencies); add_to_set(contextual_dependencies, binding.handler.contextual_dependencies);
@ -721,7 +757,7 @@ export default class ElementWrapper extends Wrapper {
// TODO dry this out — similar code for event handlers and component bindings // TODO dry this out — similar code for event handlers and component bindings
if (has_local_function) { if (has_local_function) {
const args = Array.from(contextual_dependencies).map(name => renderer.reference(name)); const args = Array.from(contextual_dependencies).map((name) => renderer.reference(name));
// need to create a block-local function that calls an instance-level function // need to create a block-local function that calls an instance-level function
if (animation_frame) { if (animation_frame) {
@ -747,23 +783,30 @@ export default class ElementWrapper extends Wrapper {
callee = handler; callee = handler;
} }
const params = Array.from(contextual_dependencies).map(name => ({ const params = Array.from(contextual_dependencies).map((name) => ({
type: 'Identifier', type: 'Identifier',
name name
})); }));
this.renderer.component.partly_hoisted.push(b` this.renderer.component.partly_hoisted.push(b`
function ${handler}(${params}) { function ${handler}(${params}) {
${binding_group.bindings.map(b => b.handler.mutation)} ${binding_group.bindings.map((b) => b.handler.mutation)}
${Array.from(dependencies) ${Array.from(dependencies)
.filter(dep => dep[0] !== '$') .filter((dep) => dep[0] !== '$')
.filter(dep => !contextual_dependencies.has(dep)) .filter((dep) => !contextual_dependencies.has(dep))
.map(dep => b`${this.renderer.invalidate(dep)};`)} .map((dep) => b`${this.renderer.invalidate(dep)};`)}
} }
`); `);
binding_group.events.forEach(name => { binding_group.events.forEach((name) => {
if (['elementresize', 'elementresizecontentbox', 'elementresizeborderbox', 'elementresizedevicepixelcontentbox'].indexOf(name) !== -1) { if (
[
'elementresize',
'elementresizecontentbox',
'elementresizeborderbox',
'elementresizedevicepixelcontentbox'
].indexOf(name) !== -1
) {
const resize_listener = block.get_unique_name(`${this.var.name}_resize_listener`); const resize_listener = block.get_unique_name(`${this.var.name}_resize_listener`);
block.add_variable(resize_listener); block.add_variable(resize_listener);
@ -791,27 +834,23 @@ export default class ElementWrapper extends Wrapper {
break; break;
} }
block.chunks.destroy.push( block.chunks.destroy.push(b`${resize_listener}();`);
b`${resize_listener}();`
);
} else { } else {
block.event_listeners.push( block.event_listeners.push(x`@listen(${this.var}, "${name}", ${callee})`);
x`@listen(${this.var}, "${name}", ${callee})`
);
} }
}); });
const some_initial_state_is_undefined = binding_group.bindings const some_initial_state_is_undefined = binding_group.bindings
.map(binding => x`${binding.snippet} === void 0`) .map((binding) => x`${binding.snippet} === void 0`)
.reduce((lhs, rhs) => x`${lhs} || ${rhs}`); .reduce((lhs, rhs) => x`${lhs} || ${rhs}`);
const should_initialise = ( const should_initialise =
this.node.name === 'select' || this.node.name === 'select' ||
binding_group.bindings.find(binding => ( binding_group.bindings.find(
(binding) =>
binding.node.name === 'indeterminate' || binding.node.name === 'indeterminate' ||
is_name_contenteditable(binding.node.name) || is_name_contenteditable(binding.node.name) ||
binding.is_readonly_media_attribute() binding.is_readonly_media_attribute()
))
); );
if (should_initialise) { if (should_initialise) {
@ -822,9 +861,7 @@ export default class ElementWrapper extends Wrapper {
} }
if (binding_group.events[0] === 'elementresize') { if (binding_group.events[0] === 'elementresize') {
block.chunks.hydrate.push( block.chunks.hydrate.push(b`@add_render_callback(() => ${callee}.call(${this.var}));`);
b`@add_render_callback(() => ${callee}.call(${this.var}));`
);
} }
if (lock) { if (lock) {
@ -852,7 +889,7 @@ export default class ElementWrapper extends Wrapper {
} }
}); });
if (this.node.attributes.some(attr => attr.is_spread)) { if (this.node.attributes.some((attr) => attr.is_spread)) {
this.add_spread_attributes(block); this.add_spread_attributes(block);
return; return;
} }
@ -868,13 +905,10 @@ export default class ElementWrapper extends Wrapper {
const initial_props = []; const initial_props = [];
const updates = []; const updates = [];
this.attributes this.attributes.forEach((attr) => {
.forEach(attr => {
const dependencies = attr.node.get_dependencies(); const dependencies = attr.node.get_dependencies();
const condition = dependencies.length > 0 const condition = dependencies.length > 0 ? block.renderer.dirty(dependencies) : null;
? block.renderer.dirty(dependencies)
: null;
if (attr instanceof SpreadAttributeWrapper) { if (attr instanceof SpreadAttributeWrapper) {
const snippet = attr.node.expression.manipulate(block); const snippet = attr.node.expression.manipulate(block);
@ -887,7 +921,9 @@ export default class ElementWrapper extends Wrapper {
initial_props.push(x`{ ${name}: ${attr.get_init(block, attr.get_value(block))} }`); initial_props.push(x`{ ${name}: ${attr.get_init(block, attr.get_value(block))} }`);
const snippet = x`{ ${name}: ${attr.should_cache ? attr.last : attr.get_value(block)} }`; const snippet = x`{ ${name}: ${attr.should_cache ? attr.last : attr.get_value(block)} }`;
updates.push(condition ? x`${attr.get_dom_update_conditions(block, condition)} && ${snippet}` : snippet); updates.push(
condition ? x`${attr.get_dom_update_conditions(block, condition)} && ${snippet}` : snippet
);
} }
}); });
@ -907,9 +943,7 @@ export default class ElementWrapper extends Wrapper {
? x`@set_dynamic_element_data(${this.node.tag_expr.manipulate(block)})` ? x`@set_dynamic_element_data(${this.node.tag_expr.manipulate(block)})`
: x`@set_attributes`; : x`@set_attributes`;
block.chunks.hydrate.push( block.chunks.hydrate.push(b`${fn}(${this.var}, ${this.element_data_name});`);
b`${fn}(${this.var}, ${this.element_data_name});`
);
if (this.has_dynamic_attribute) { if (this.has_dynamic_attribute) {
block.chunks.update.push(b` block.chunks.update.push(b`
@ -933,11 +967,22 @@ export default class ElementWrapper extends Wrapper {
`); `);
block.chunks.update.push(b` block.chunks.update.push(b`
if (${block.renderer.dirty(Array.from(dependencies))} && 'value' in ${this.element_data_name}) (${this.element_data_name}.multiple ? @select_options : @select_option)(${this.var}, ${this.element_data_name}.value); if (${block.renderer.dirty(Array.from(dependencies))} && 'value' in ${this.element_data_name}) (${
this.element_data_name
}.multiple ? @select_options : @select_option)(${this.var}, ${this.element_data_name}.value);
`); `);
} else if (this.node.name === 'input' && this.attributes.find(attr => attr.node.name === 'value')) { } else if (
this.node.name === 'input' &&
this.attributes.find((attr) => attr.node.name === 'value')
) {
const type = this.node.get_static_attribute_value('type'); const type = this.node.get_static_attribute_value('type');
if (type === null || type === '' || type === 'text' || type === 'email' || type === 'password') { if (
type === null ||
type === '' ||
type === 'text' ||
type === 'email' ||
type === 'password'
) {
block.chunks.mount.push(b` block.chunks.mount.push(b`
if ('value' in ${this.element_data_name}) { if ('value' in ${this.element_data_name}) {
${this.var}.value = ${this.element_data_name}.value; ${this.var}.value = ${this.element_data_name}.value;
@ -982,9 +1027,7 @@ export default class ElementWrapper extends Wrapper {
? x`@set_dynamic_element_data(${this.node.tag_expr.manipulate(block)})` ? x`@set_dynamic_element_data(${this.node.tag_expr.manipulate(block)})`
: x`@set_attributes`; : x`@set_attributes`;
block.chunks.hydrate.push( block.chunks.hydrate.push(b`${fn}(${this.var}, {${static_attributes}});`);
b`${fn}(${this.var}, {${static_attributes}});`
);
} }
add_transitions(block: Block) { add_transitions(block: Block) {
@ -994,9 +1037,7 @@ export default class ElementWrapper extends Wrapper {
if (intro === outro) { if (intro === outro) {
// bidirectional transition // bidirectional transition
const name = block.get_unique_name(`${this.var.name}_transition`); const name = block.get_unique_name(`${this.var.name}_transition`);
const snippet = intro.expression const snippet = intro.expression ? intro.expression.manipulate(block) : x`{}`;
? intro.expression.manipulate(block)
: x`{}`;
block.add_variable(name); block.add_variable(name);
@ -1039,9 +1080,7 @@ export default class ElementWrapper extends Wrapper {
if (intro) { if (intro) {
block.add_variable(intro_name); block.add_variable(intro_name);
const snippet = intro.expression const snippet = intro.expression ? intro.expression.manipulate(block) : x`{}`;
? intro.expression.manipulate(block)
: x`{}`;
const fn = this.renderer.reference(intro.name); const fn = this.renderer.reference(intro.name);
@ -1082,9 +1121,7 @@ export default class ElementWrapper extends Wrapper {
if (outro) { if (outro) {
block.add_variable(outro_name); block.add_variable(outro_name);
const snippet = outro.expression const snippet = outro.expression ? outro.expression.manipulate(block) : x`{}`;
? outro.expression.manipulate(block)
: x`{}`;
const fn = this.renderer.reference(outro.name); const fn = this.renderer.reference(outro.name);
@ -1114,7 +1151,10 @@ export default class ElementWrapper extends Wrapper {
} }
} }
if ((intro && intro.expression && intro.expression.dependencies.size) || (outro && outro.expression && outro.expression.dependencies.size)) { if (
(intro && intro.expression && intro.expression.dependencies.size) ||
(outro && outro.expression && outro.expression.dependencies.size)
) {
block.maintain_context = true; block.maintain_context = true;
} }
} }
@ -1170,8 +1210,8 @@ export default class ElementWrapper extends Wrapper {
} }
add_classes(block: Block) { add_classes(block: Block) {
const has_spread = this.node.attributes.some(attr => attr.is_spread); const has_spread = this.node.attributes.some((attr) => attr.is_spread);
this.node.classes.forEach(class_directive => { this.node.classes.forEach((class_directive) => {
const { expression, name } = class_directive; const { expression, name } = class_directive;
let snippet: Node | string; let snippet: Node | string;
let dependencies: Set<string>; let dependencies: Set<string>;
@ -1213,7 +1253,7 @@ export default class ElementWrapper extends Wrapper {
} }
add_styles(block: Block) { add_styles(block: Block) {
const has_spread = this.node.attributes.some(attr => attr.is_spread); const has_spread = this.node.attributes.some((attr) => attr.is_spread);
let style_changed_var: Identifier | undefined; let style_changed_var: Identifier | undefined;
const maybe_create_style_changed_var = () => { const maybe_create_style_changed_var = () => {
@ -1234,15 +1274,14 @@ export default class ElementWrapper extends Wrapper {
block.add_variable(cached_snippet, snippet); block.add_variable(cached_snippet, snippet);
} }
const updater = b`@set_style(${this.var}, "${name}", ${should_cache ? cached_snippet : snippet}, ${important ? 1 : null})`; const updater = b`@set_style(${this.var}, "${name}", ${
should_cache ? cached_snippet : snippet
}, ${important ? 1 : null})`;
block.chunks.hydrate.push(updater); block.chunks.hydrate.push(updater);
const self_deps = expression.dynamic_dependencies(); const self_deps = expression.dynamic_dependencies();
const all_deps = new Set([ const all_deps = new Set([...self_deps, ...this.dynamic_style_dependencies]);
...self_deps,
...this.dynamic_style_dependencies
]);
let condition = block.renderer.dirty([...all_deps]); let condition = block.renderer.dirty([...all_deps]);
@ -1253,8 +1292,7 @@ export default class ElementWrapper extends Wrapper {
block.chunks.update.push(b` block.chunks.update.push(b`
if (${condition}) { if (${condition}) {
${cached_snippet} = ${snippet}; ${cached_snippet} = ${snippet};
}` }`);
);
} }
block.chunks.update.push(updater); block.chunks.update.push(updater);
} else { } else {
@ -1294,8 +1332,16 @@ export default class ElementWrapper extends Wrapper {
const regex_backticks = /`/g; const regex_backticks = /`/g;
const regex_dollar_signs = /\$/g; const regex_dollar_signs = /\$/g;
function to_html(wrappers: Array<CommentWrapper | ElementWrapper | TextWrapper | MustacheTagWrapper | RawMustacheTagWrapper>, block: Block, literal: any, state: any, can_use_raw_text?: boolean) { function to_html(
wrappers.forEach(wrapper => { wrappers: Array<
CommentWrapper | ElementWrapper | TextWrapper | MustacheTagWrapper | RawMustacheTagWrapper
>,
block: Block,
literal: any,
state: any,
can_use_raw_text?: boolean
) {
wrappers.forEach((wrapper) => {
if (wrapper instanceof CommentWrapper) { if (wrapper instanceof CommentWrapper) {
state.quasi.value.raw += wrapper.text(); state.quasi.value.raw += wrapper.text();
} else if (wrapper instanceof TextWrapper) { } else if (wrapper instanceof TextWrapper) {
@ -1310,11 +1356,8 @@ function to_html(wrappers: Array<CommentWrapper | ElementWrapper | TextWrapper |
const parent = wrapper.node.parent as Element; const parent = wrapper.node.parent as Element;
const raw = parent && ( const raw =
parent.name === 'script' || parent && (parent.name === 'script' || parent.name === 'style' || can_use_raw_text);
parent.name === 'style' ||
can_use_raw_text
);
state.quasi.value.raw += (raw ? wrapper.data : escape_html(wrapper.data)) state.quasi.value.raw += (raw ? wrapper.data : escape_html(wrapper.data))
.replace(regex_backslashes, '\\\\') .replace(regex_backslashes, '\\\\')
@ -1356,14 +1399,18 @@ function to_html(wrappers: Array<CommentWrapper | ElementWrapper | TextWrapper |
// Two or more leading newlines are required to restore the leading newline immediately after `<pre>`. // Two or more leading newlines are required to restore the leading newline immediately after `<pre>`.
// see https://html.spec.whatwg.org/multipage/grouping-content.html#the-pre-element // see https://html.spec.whatwg.org/multipage/grouping-content.html#the-pre-element
const first = wrapper.fragment.nodes[0]; const first = wrapper.fragment.nodes[0];
if (first && first.node.type === 'Text' && regex_starts_with_newline.test(first.node.data)) { if (
first &&
first.node.type === 'Text' &&
regex_starts_with_newline.test(first.node.data)
) {
state.quasi.value.raw += '\n'; state.quasi.value.raw += '\n';
} }
} }
if (is_empty_textarea) { if (is_empty_textarea) {
// The <textarea> renders the value attribute as content because the content is stored in the value attribute. // The <textarea> renders the value attribute as content because the content is stored in the value attribute.
const value_attribute = wrapper.attributes.find(attr => attr.node.name === 'value'); const value_attribute = wrapper.attributes.find((attr) => attr.node.name === 'value');
if (value_attribute) { if (value_attribute) {
// Two or more leading newlines are required to restore the leading newline immediately after `<textarea>`. // Two or more leading newlines are required to restore the leading newline immediately after `<textarea>`.
// see https://html.spec.whatwg.org/multipage/syntax.html#element-restrictions // see https://html.spec.whatwg.org/multipage/syntax.html#element-restrictions
@ -1375,7 +1422,12 @@ function to_html(wrappers: Array<CommentWrapper | ElementWrapper | TextWrapper |
} }
} }
to_html(wrapper.fragment.nodes as Array<ElementWrapper | TextWrapper>, block, literal, state); to_html(
wrapper.fragment.nodes as Array<ElementWrapper | TextWrapper>,
block,
literal,
state
);
state.quasi.value.raw += `</${nodeName}>`; state.quasi.value.raw += `</${nodeName}>`;
} else { } else {
@ -1385,8 +1437,13 @@ function to_html(wrappers: Array<CommentWrapper | ElementWrapper | TextWrapper |
}); });
} }
function to_html_for_attr_value(attr: AttributeWrapper | StyleAttributeWrapper | SpreadAttributeWrapper, block: Block, literal: any, state: any) { function to_html_for_attr_value(
attr.node.chunks.forEach(chunk => { attr: AttributeWrapper | StyleAttributeWrapper | SpreadAttributeWrapper,
block: Block,
literal: any,
state: any
) {
attr.node.chunks.forEach((chunk) => {
if (chunk.type === 'Text') { if (chunk.type === 'Text') {
state.quasi.value.raw += escape_html(chunk.data); state.quasi.value.raw += escape_html(chunk.data);
} else { } else {

@ -51,7 +51,10 @@ function trimmable_at(child: INode, next_sibling: Wrapper): boolean {
// Whitespace is trimmable if one of the following is true: // Whitespace is trimmable if one of the following is true:
// The child and its sibling share a common nearest each block (not at an each block boundary) // The child and its sibling share a common nearest each block (not at an each block boundary)
// The next sibling's previous node is an each block // The next sibling's previous node is an each block
return (next_sibling.node.find_nearest(/EachBlock/) === child.find_nearest(/EachBlock/)) || next_sibling.node.prev.type === 'EachBlock'; return (
next_sibling.node.find_nearest(/EachBlock/) === child.find_nearest(/EachBlock/) ||
next_sibling.node.prev.type === 'EachBlock'
);
} }
export default class FragmentWrapper { export default class FragmentWrapper {
@ -95,9 +98,11 @@ export default class FragmentWrapper {
// We want to remove trailing whitespace inside an element/component/block, // We want to remove trailing whitespace inside an element/component/block,
// *unless* there is no whitespace between this node and its next sibling // *unless* there is no whitespace between this node and its next sibling
if (this.nodes.length === 0) { if (this.nodes.length === 0) {
const should_trim = ( const should_trim = next_sibling
next_sibling ? (next_sibling.node.type === 'Text' && regex_starts_with_whitespace.test(next_sibling.node.data) && trimmable_at(child, next_sibling)) : !child.has_ancestor('EachBlock') ? next_sibling.node.type === 'Text' &&
); regex_starts_with_whitespace.test(next_sibling.node.data) &&
trimmable_at(child, next_sibling)
: !child.has_ancestor('EachBlock');
if (should_trim && !child.keep_space()) { if (should_trim && !child.keep_space()) {
data = trim_end(data); data = trim_end(data);
@ -116,15 +121,22 @@ export default class FragmentWrapper {
this.nodes.unshift(wrapper); this.nodes.unshift(wrapper);
link(last_child, last_child = wrapper); link(last_child, (last_child = wrapper));
} else { } else {
const Wrapper = wrappers[child.type]; const Wrapper = wrappers[child.type];
if (!Wrapper || (child.type === 'Comment' && !renderer.options.preserveComments)) continue; if (!Wrapper || (child.type === 'Comment' && !renderer.options.preserveComments)) continue;
const wrapper = new Wrapper(renderer, block, parent, child, strip_whitespace, last_child || next_sibling); const wrapper = new Wrapper(
renderer,
block,
parent,
child,
strip_whitespace,
last_child || next_sibling
);
this.nodes.unshift(wrapper); this.nodes.unshift(wrapper);
link(last_child, last_child = wrapper); link(last_child, (last_child = wrapper));
} }
} }

@ -34,15 +34,15 @@ export default class HeadWrapper extends Wrapper {
let nodes: Identifier; let nodes: Identifier;
if (this.renderer.options.hydratable && this.fragment.nodes.length) { if (this.renderer.options.hydratable && this.fragment.nodes.length) {
nodes = block.get_unique_name('head_nodes'); nodes = block.get_unique_name('head_nodes');
block.chunks.claim.push(b`const ${nodes} = @head_selector('${this.node.id}', @_document.head);`); block.chunks.claim.push(
b`const ${nodes} = @head_selector('${this.node.id}', @_document.head);`
);
} }
this.fragment.render(block, x`@_document.head` as unknown as Identifier, nodes); this.fragment.render(block, x`@_document.head` as unknown as Identifier, nodes);
if (nodes && this.renderer.options.hydratable) { if (nodes && this.renderer.options.hydratable) {
block.chunks.claim.push( block.chunks.claim.push(b`${nodes}.forEach(@detach);`);
b`${nodes}.forEach(@detach);`
);
} }
} }
} }

@ -16,9 +16,7 @@ import { add_const_tags, add_const_tags_context } from './shared/add_const_tags'
type DetachingOrNull = 'detaching' | null; type DetachingOrNull = 'detaching' | null;
function is_else_if(node: ElseBlock) { function is_else_if(node: ElseBlock) {
return ( return node && node.children.length === 1 && node.children[0].type === 'IfBlock';
node && node.children.length === 1 && node.children[0].type === 'IfBlock'
);
} }
class IfBlockBranch extends Wrapper { class IfBlockBranch extends Wrapper {
@ -43,7 +41,7 @@ class IfBlockBranch extends Wrapper {
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
const { expression } = (node as IfBlock); const { expression } = node as IfBlock;
const is_else = !expression; const is_else = !expression;
if (expression) { if (expression) {
@ -62,7 +60,7 @@ class IfBlockBranch extends Wrapper {
if (should_cache) { if (should_cache) {
this.condition = block.get_unique_name('show_if'); this.condition = block.get_unique_name('show_if');
this.snippet = (expression.manipulate(block) as Node); this.snippet = expression.manipulate(block) as Node;
} else { } else {
this.condition = expression.manipulate(block); this.condition = expression.manipulate(block);
} }
@ -78,12 +76,21 @@ class IfBlockBranch extends Wrapper {
type: (node as IfBlock).expression ? 'if' : 'else' type: (node as IfBlock).expression ? 'if' : 'else'
}); });
this.fragment = new FragmentWrapper(renderer, this.block, node.children, parent, strip_whitespace, next_sibling); this.fragment = new FragmentWrapper(
renderer,
this.block,
node.children,
parent,
strip_whitespace,
next_sibling
);
this.is_dynamic = this.block.dependencies.size > 0; this.is_dynamic = this.block.dependencies.size > 0;
if (node.const_tags.length > 0) { if (node.const_tags.length > 0) {
this.get_ctx_name = parent.renderer.component.get_unique_name(is_else ? 'get_else_ctx' : 'get_if_ctx'); this.get_ctx_name = parent.renderer.component.get_unique_name(
is_else ? 'get_else_ctx' : 'get_if_ctx'
);
} }
} }
} }
@ -113,14 +120,7 @@ export default class IfBlockWrapper extends Wrapper {
let has_outros = false; let has_outros = false;
const create_branches = (node: IfBlock) => { const create_branches = (node: IfBlock) => {
const branch = new IfBlockBranch( const branch = new IfBlockBranch(renderer, block, this, node, strip_whitespace, next_sibling);
renderer,
block,
this,
node,
strip_whitespace,
next_sibling
);
this.branches.push(branch); this.branches.push(branch);
@ -169,7 +169,7 @@ export default class IfBlockWrapper extends Wrapper {
create_branches(this.node); create_branches(this.node);
blocks.forEach(block => { blocks.forEach((block) => {
block.has_update_method = is_dynamic; block.has_update_method = is_dynamic;
block.has_intro_method = has_intros; block.has_intro_method = has_intros;
block.has_outro_method = has_outros; block.has_outro_method = has_outros;
@ -178,19 +178,17 @@ export default class IfBlockWrapper extends Wrapper {
push_array(renderer.blocks, blocks); push_array(renderer.blocks, blocks);
} }
render( render(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
block: Block,
parent_node: Identifier,
parent_nodes: Identifier
) {
const name = this.var; const name = this.var;
const needs_anchor = this.next ? !this.next.is_dom_node() : !parent_node || !this.parent.is_dom_node(); const needs_anchor = this.next
? !this.next.is_dom_node()
: !parent_node || !this.parent.is_dom_node();
const anchor = needs_anchor const anchor = needs_anchor
? block.get_unique_name(`${this.var.name}_anchor`) ? block.get_unique_name(`${this.var.name}_anchor`)
: (this.next && this.next.var) || 'null'; : (this.next && this.next.var) || 'null';
const has_else = !(this.branches[this.branches.length - 1].condition); const has_else = !this.branches[this.branches.length - 1].condition;
const if_exists_condition = has_else ? null : name; const if_exists_condition = has_else ? null : name;
const dynamic = this.branches[0].block.has_update_method; // can use [0] as proxy for all, since they necessarily have the same value const dynamic = this.branches[0].block.has_update_method; // can use [0] as proxy for all, since they necessarily have the same value
@ -198,7 +196,7 @@ export default class IfBlockWrapper extends Wrapper {
const has_outros = this.branches[0].block.has_outro_method; const has_outros = this.branches[0].block.has_outro_method;
const has_transitions = has_intros || has_outros; const has_transitions = has_intros || has_outros;
this.branches.forEach(branch => { this.branches.forEach((branch) => {
if (branch.get_ctx_name) { if (branch.get_ctx_name) {
this.renderer.blocks.push(b` this.renderer.blocks.push(b`
function ${branch.get_ctx_name}(#ctx) { function ${branch.get_ctx_name}(#ctx) {
@ -215,12 +213,19 @@ export default class IfBlockWrapper extends Wrapper {
const detaching: DetachingOrNull = parent_node && !is_head(parent_node) ? null : 'detaching'; const detaching: DetachingOrNull = parent_node && !is_head(parent_node) ? null : 'detaching';
if (this.node.else) { if (this.node.else) {
this.branches.forEach(branch => { this.branches.forEach((branch) => {
if (branch.snippet) block.add_variable(branch.condition); if (branch.snippet) block.add_variable(branch.condition);
}); });
if (has_outros) { if (has_outros) {
this.render_compound_with_outros(block, parent_node, parent_nodes, dynamic, vars, detaching); this.render_compound_with_outros(
block,
parent_node,
parent_nodes,
dynamic,
vars,
detaching
);
block.chunks.outro.push(b`@transition_out(${name});`); block.chunks.outro.push(b`@transition_out(${name});`);
} else { } else {
@ -242,13 +247,9 @@ export default class IfBlockWrapper extends Wrapper {
if (parent_nodes && this.renderer.options.hydratable) { if (parent_nodes && this.renderer.options.hydratable) {
if (if_exists_condition) { if (if_exists_condition) {
block.chunks.claim.push( block.chunks.claim.push(b`if (${if_exists_condition}) ${name}.l(${parent_nodes});`);
b`if (${if_exists_condition}) ${name}.l(${parent_nodes});`
);
} else { } else {
block.chunks.claim.push( block.chunks.claim.push(b`${name}.l(${parent_nodes});`);
b`${name}.l(${parent_nodes});`
);
} }
} }
@ -265,7 +266,7 @@ export default class IfBlockWrapper extends Wrapper {
); );
} }
this.branches.forEach(branch => { this.branches.forEach((branch) => {
branch.fragment.render(branch.block, null, x`#nodes` as unknown as Identifier); branch.fragment.render(branch.block, null, x`#nodes` as unknown as Identifier);
}); });
} }
@ -280,8 +281,10 @@ export default class IfBlockWrapper extends Wrapper {
) { ) {
const select_block_type = this.renderer.component.get_unique_name('select_block_type'); const select_block_type = this.renderer.component.get_unique_name('select_block_type');
const current_block_type = block.get_unique_name('current_block_type'); const current_block_type = block.get_unique_name('current_block_type');
const need_select_block_ctx = this.branches.some(branch => branch.get_ctx_name); const need_select_block_ctx = this.branches.some((branch) => branch.get_ctx_name);
const select_block_ctx = need_select_block_ctx ? block.get_unique_name('select_block_ctx') : null; const select_block_ctx = need_select_block_ctx
? block.get_unique_name('select_block_ctx')
: null;
const if_ctx = select_block_ctx ? x`${select_block_ctx}(#ctx, ${current_block_type})` : x`#ctx`; const if_ctx = select_block_ctx ? x`${select_block_ctx}(#ctx, ${current_block_type})` : x`#ctx`;
const get_block = has_else const get_block = has_else
@ -292,9 +295,14 @@ export default class IfBlockWrapper extends Wrapper {
block.chunks.init.push(b` block.chunks.init.push(b`
function ${select_block_type}(#ctx, #dirty) { function ${select_block_type}(#ctx, #dirty) {
${this.branches.map(({ dependencies, condition, snippet }) => { ${this.branches.map(({ dependencies, condition, snippet }) => {
return b`${snippet && dependencies.length > 0 ? b`if (${block.renderer.dirty(dependencies)}) ${condition} = null;` : null}`; return b`${
snippet && dependencies.length > 0
? b`if (${block.renderer.dirty(dependencies)}) ${condition} = null;`
: null
}`;
})} })}
${this.branches.map(({ condition, snippet, block }) => condition ${this.branches.map(({ condition, snippet, block }) =>
condition
? b` ? b`
${snippet && b`if (${condition} == null) ${condition} = !!${snippet}`} ${snippet && b`if (${condition} == null) ${condition} = !!${snippet}`}
if (${condition}) return ${block.name};` if (${condition}) return ${block.name};`
@ -305,23 +313,27 @@ export default class IfBlockWrapper extends Wrapper {
} else { } else {
block.chunks.init.push(b` block.chunks.init.push(b`
function ${select_block_type}(#ctx, #dirty) { function ${select_block_type}(#ctx, #dirty) {
${this.branches.map(({ condition, snippet, block }) => condition ${this.branches.map(({ condition, snippet, block }) =>
condition
? b`if (${snippet || condition}) return ${block.name};` ? b`if (${snippet || condition}) return ${block.name};`
: b`return ${block.name};`)} : b`return ${block.name};`
)}
} }
`); `);
} }
if (need_select_block_ctx) { if (need_select_block_ctx) {
// if all branches needs create a context // if all branches needs create a context
if (this.branches.every(branch => branch.get_ctx_name)) { if (this.branches.every((branch) => branch.get_ctx_name)) {
block.chunks.init.push(b` block.chunks.init.push(b`
function ${select_block_ctx}(#ctx, #type) { function ${select_block_ctx}(#ctx, #type) {
${this.branches.map(({ condition, get_ctx_name, block }) => { ${this.branches
.map(({ condition, get_ctx_name, block }) => {
return condition return condition
? b`if (#type === ${block.name}) return ${get_ctx_name}(#ctx);` ? b`if (#type === ${block.name}) return ${get_ctx_name}(#ctx);`
: b`return ${get_ctx_name}(#ctx);`; : b`return ${get_ctx_name}(#ctx);`;
}).filter(Boolean)} })
.filter(Boolean)}
} }
`); `);
} else { } else {
@ -329,11 +341,13 @@ export default class IfBlockWrapper extends Wrapper {
// this code is simpler // this code is simpler
block.chunks.init.push(b` block.chunks.init.push(b`
function ${select_block_ctx}(#ctx, #type) { function ${select_block_ctx}(#ctx, #type) {
${this.branches.map(({ get_ctx_name, block }) => { ${this.branches
.map(({ get_ctx_name, block }) => {
return get_ctx_name return get_ctx_name
? b`if (#type === ${block.name}) return ${get_ctx_name}(#ctx);` ? b`if (#type === ${block.name}) return ${get_ctx_name}(#ctx);`
: null; : null;
}).filter(Boolean)} })
.filter(Boolean)}
return #ctx; return #ctx;
} }
`); `);
@ -353,9 +367,7 @@ export default class IfBlockWrapper extends Wrapper {
b`if (${if_exists_condition}) ${name}.m(${initial_mount_node}, ${anchor_node});` b`if (${if_exists_condition}) ${name}.m(${initial_mount_node}, ${anchor_node});`
); );
} else { } else {
block.chunks.mount.push( block.chunks.mount.push(b`${name}.m(${initial_mount_node}, ${anchor_node});`);
b`${name}.m(${initial_mount_node}, ${anchor_node});`
);
} }
if (this.needs_update) { if (this.needs_update) {
@ -422,9 +434,13 @@ export default class IfBlockWrapper extends Wrapper {
const previous_block_index = block.get_unique_name('previous_block_index'); const previous_block_index = block.get_unique_name('previous_block_index');
const if_block_creators = block.get_unique_name('if_block_creators'); const if_block_creators = block.get_unique_name('if_block_creators');
const if_blocks = block.get_unique_name('if_blocks'); const if_blocks = block.get_unique_name('if_blocks');
const need_select_block_ctx = this.branches.some(branch => branch.get_ctx_name); const need_select_block_ctx = this.branches.some((branch) => branch.get_ctx_name);
const select_block_ctx = need_select_block_ctx ? block.get_unique_name('select_block_ctx') : null; const select_block_ctx = need_select_block_ctx
const if_ctx = select_block_ctx ? x`${select_block_ctx}(#ctx, ${current_block_type_index})` : x`#ctx`; ? block.get_unique_name('select_block_ctx')
: null;
const if_ctx = select_block_ctx
? x`${select_block_ctx}(#ctx, ${current_block_type_index})`
: x`#ctx`;
const if_current_block_type_index = has_else const if_current_block_type_index = has_else
? (nodes: Node[]) => nodes ? (nodes: Node[]) => nodes
@ -435,45 +451,55 @@ export default class IfBlockWrapper extends Wrapper {
block.chunks.init.push(b` block.chunks.init.push(b`
const ${if_block_creators} = [ const ${if_block_creators} = [
${this.branches.map(branch => branch.block.name)} ${this.branches.map((branch) => branch.block.name)}
]; ];
const ${if_blocks} = []; const ${if_blocks} = [];
${this.needs_update ${
this.needs_update
? b` ? b`
function ${select_block_type}(#ctx, #dirty) { function ${select_block_type}(#ctx, #dirty) {
${this.branches.map(({ dependencies, condition, snippet }) => { ${this.branches.map(({ dependencies, condition, snippet }) => {
return b`${snippet && dependencies.length > 0 ? b`if (${block.renderer.dirty(dependencies)}) ${condition} = null;` : null}`; return b`${
snippet && dependencies.length > 0
? b`if (${block.renderer.dirty(dependencies)}) ${condition} = null;`
: null
}`;
})} })}
${this.branches.map(({ condition, snippet }, i) => condition ${this.branches.map(({ condition, snippet }, i) =>
condition
? b` ? b`
${snippet && b`if (${condition} == null) ${condition} = !!${snippet}`} ${snippet && b`if (${condition} == null) ${condition} = !!${snippet}`}
if (${condition}) return ${i};` if (${condition}) return ${i};`
: b`return ${i};`)} : b`return ${i};`
)}
${!has_else && b`return -1;`} ${!has_else && b`return -1;`}
} }
` `
: b` : b`
function ${select_block_type}(#ctx, #dirty) { function ${select_block_type}(#ctx, #dirty) {
${this.branches.map(({ condition, snippet }, i) => condition ${this.branches.map(({ condition, snippet }, i) =>
? b`if (${snippet || condition}) return ${i};` condition ? b`if (${snippet || condition}) return ${i};` : b`return ${i};`
: b`return ${i};`)} )}
${!has_else && b`return -1;`} ${!has_else && b`return -1;`}
} }
`} `
}
`); `);
if (need_select_block_ctx) { if (need_select_block_ctx) {
// if all branches needs create a context // if all branches needs create a context
if (this.branches.every(branch => branch.get_ctx_name)) { if (this.branches.every((branch) => branch.get_ctx_name)) {
block.chunks.init.push(b` block.chunks.init.push(b`
function ${select_block_ctx}(#ctx, #index) { function ${select_block_ctx}(#ctx, #index) {
${this.branches.map(({ condition, get_ctx_name }, i) => { ${this.branches
.map(({ condition, get_ctx_name }, i) => {
return condition return condition
? b`if (#index === ${i}) return ${get_ctx_name}(#ctx);` ? b`if (#index === ${i}) return ${get_ctx_name}(#ctx);`
: b`return ${get_ctx_name}(#ctx);`; : b`return ${get_ctx_name}(#ctx);`;
}).filter(Boolean)} })
.filter(Boolean)}
} }
`); `);
} else { } else {
@ -481,11 +507,11 @@ export default class IfBlockWrapper extends Wrapper {
// this code is simpler // this code is simpler
block.chunks.init.push(b` block.chunks.init.push(b`
function ${select_block_ctx}(#ctx, #index) { function ${select_block_ctx}(#ctx, #index) {
${this.branches.map(({ get_ctx_name }, i) => { ${this.branches
return get_ctx_name .map(({ get_ctx_name }, i) => {
? b`if (#index === ${i}) return ${get_ctx_name}(#ctx);` return get_ctx_name ? b`if (#index === ${i}) return ${get_ctx_name}(#ctx);` : null;
: null; })
}).filter(Boolean)} .filter(Boolean)}
return #ctx; return #ctx;
} }
`); `);
@ -608,9 +634,7 @@ export default class IfBlockWrapper extends Wrapper {
const initial_mount_node = parent_node || '#target'; const initial_mount_node = parent_node || '#target';
const anchor_node = parent_node ? 'null' : '#anchor'; const anchor_node = parent_node ? 'null' : '#anchor';
block.chunks.mount.push( block.chunks.mount.push(b`if (${name}) ${name}.m(${initial_mount_node}, ${anchor_node});`);
b`if (${name}) ${name}.m(${initial_mount_node}, ${anchor_node});`
);
if (branch.dependencies.length > 0) { if (branch.dependencies.length > 0) {
const update_mount_node = this.get_update_mount_node(anchor); const update_mount_node = this.get_update_mount_node(anchor);
@ -618,7 +642,8 @@ export default class IfBlockWrapper extends Wrapper {
const enter = b` const enter = b`
if (${name}) { if (${name}) {
${dynamic && b`${name}.p(${if_ctx}, #dirty);`} ${dynamic && b`${name}.p(${if_ctx}, #dirty);`}
${has_transitions && ${
has_transitions &&
b`if (${block.renderer.dirty(branch.dependencies)}) { b`if (${block.renderer.dirty(branch.dependencies)}) {
@transition_in(${name}, 1); @transition_in(${name}, 1);
}` }`
@ -632,7 +657,11 @@ export default class IfBlockWrapper extends Wrapper {
`; `;
if (branch.snippet) { if (branch.snippet) {
block.chunks.update.push(b`if (${block.renderer.dirty(branch.dependencies)}) ${branch.condition} = ${branch.snippet}`); block.chunks.update.push(
b`if (${block.renderer.dirty(branch.dependencies)}) ${branch.condition} = ${
branch.snippet
}`
);
} }
// no `p()` here — we don't want to update outroing nodes, // no `p()` here — we don't want to update outroing nodes,

@ -23,7 +23,12 @@ import compiler_warnings from '../../../compiler_warnings';
import { namespaces } from '../../../../utils/namespaces'; import { namespaces } from '../../../../utils/namespaces';
import { extract_ignores_above_node } from '../../../../utils/extract_svelte_ignore'; import { extract_ignores_above_node } from '../../../../utils/extract_svelte_ignore';
type SlotDefinition = { block: Block; scope: TemplateScope; get_context?: Node; get_changes?: Node }; type SlotDefinition = {
block: Block;
scope: TemplateScope;
get_context?: Node;
get_changes?: Node;
};
const regex_invalid_variable_identifier_characters = /[^a-zA-Z_$]/g; const regex_invalid_variable_identifier_characters = /[^a-zA-Z_$]/g;
@ -48,11 +53,11 @@ export default class InlineComponentWrapper extends Wrapper {
block.add_dependencies(this.node.expression.dependencies); block.add_dependencies(this.node.expression.dependencies);
} }
this.node.attributes.forEach(attr => { this.node.attributes.forEach((attr) => {
block.add_dependencies(attr.dependencies); block.add_dependencies(attr.dependencies);
}); });
this.node.bindings.forEach(binding => { this.node.bindings.forEach((binding) => {
if (binding.is_contextual) { if (binding.is_contextual) {
mark_each_block_bindings(this, binding); mark_each_block_bindings(this, binding);
} }
@ -60,33 +65,44 @@ export default class InlineComponentWrapper extends Wrapper {
block.add_dependencies(binding.expression.dependencies); block.add_dependencies(binding.expression.dependencies);
}); });
this.node.handlers.forEach(handler => { this.node.handlers.forEach((handler) => {
if (handler.expression) { if (handler.expression) {
block.add_dependencies(handler.expression.dependencies); block.add_dependencies(handler.expression.dependencies);
} }
}); });
this.node.css_custom_properties.forEach(attr => { this.node.css_custom_properties.forEach((attr) => {
block.add_dependencies(attr.dependencies); block.add_dependencies(attr.dependencies);
}); });
this.var = { this.var = {
type: 'Identifier', type: 'Identifier',
name: ( name: (this.node.name === 'svelte:self'
this.node.name === 'svelte:self' ? renderer.component.name.name : ? renderer.component.name.name
this.node.name === 'svelte:component' ? 'switch_instance' : : this.node.name === 'svelte:component'
sanitize(this.node.name) ? 'switch_instance'
: sanitize(this.node.name)
).toLowerCase() ).toLowerCase()
}; };
if (this.node.children.length) { if (this.node.children.length) {
this.node.lets.forEach(l => { this.node.lets.forEach((l) => {
extract_names(l.value || l.name).forEach(name => { extract_names(l.value || l.name).forEach((name) => {
renderer.add_to_context(name, true); renderer.add_to_context(name, true);
}); });
}); });
this.children = this.node.children.map(child => new SlotTemplateWrapper(renderer, block, this, child as SlotTemplate, strip_whitespace, next_sibling)); this.children = this.node.children.map(
(child) =>
new SlotTemplateWrapper(
renderer,
block,
this,
child as SlotTemplate,
strip_whitespace,
next_sibling
)
);
} }
block.add_outro(); block.add_outro();
@ -117,11 +133,7 @@ export default class InlineComponentWrapper extends Wrapper {
this.renderer.component.pop_ignores(); this.renderer.component.pop_ignores();
} }
render( render(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
block: Block,
parent_node: Identifier,
parent_nodes: Identifier
) {
this.warn_if_reactive(); this.warn_if_reactive();
const { renderer } = this; const { renderer } = this;
@ -143,7 +155,7 @@ export default class InlineComponentWrapper extends Wrapper {
let props: Identifier | undefined; let props: Identifier | undefined;
const name_changes = block.get_unique_name(`${name.name}_changes`); const name_changes = block.get_unique_name(`${name.name}_changes`);
const uses_spread = !!this.node.attributes.find(a => a.is_spread); const uses_spread = !!this.node.attributes.find((a) => a.is_spread);
// removing empty slot // removing empty slot
for (const slot of this.slots.keys()) { for (const slot of this.slots.keys()) {
@ -156,16 +168,21 @@ export default class InlineComponentWrapper extends Wrapper {
const has_css_custom_properties = this.node.css_custom_properties.length > 0; const has_css_custom_properties = this.node.css_custom_properties.length > 0;
const is_svg_namespace = this.node.namespace === namespaces.svg; const is_svg_namespace = this.node.namespace === namespaces.svg;
const css_custom_properties_wrapper_element = is_svg_namespace ? 'g' : 'div'; const css_custom_properties_wrapper_element = is_svg_namespace ? 'g' : 'div';
const css_custom_properties_wrapper = has_css_custom_properties ? block.get_unique_name(css_custom_properties_wrapper_element) : null; const css_custom_properties_wrapper = has_css_custom_properties
? block.get_unique_name(css_custom_properties_wrapper_element)
: null;
if (has_css_custom_properties) { if (has_css_custom_properties) {
block.add_variable(css_custom_properties_wrapper); block.add_variable(css_custom_properties_wrapper);
} }
const initial_props = this.slots.size > 0 const initial_props =
this.slots.size > 0
? [ ? [
p`$$slots: { p`$$slots: {
${Array.from(this.slots).map(([name, slot]) => { ${Array.from(this.slots).map(([name, slot]) => {
return p`${name}: [${slot.block.name}, ${slot.get_context || null}, ${slot.get_changes || null}]`; return p`${name}: [${slot.block.name}, ${slot.get_context || null}, ${
slot.get_changes || null
}]`;
})} })}
}`, }`,
p`$$scope: { p`$$scope: {
@ -177,7 +194,7 @@ export default class InlineComponentWrapper extends Wrapper {
const attribute_object = uses_spread const attribute_object = uses_spread
? x`{ ${initial_props} }` ? x`{ ${initial_props} }`
: x`{ : x`{
${this.node.attributes.map(attr => p`${attr.name}: ${attr.get_value(block)}`)}, ${this.node.attributes.map((attr) => p`${attr.name}: ${attr.get_value(block)}`)},
${initial_props} ${initial_props}
}`; }`;
@ -199,8 +216,8 @@ export default class InlineComponentWrapper extends Wrapper {
} }
const fragment_dependencies = new Set(this.slots.size ? ['$$scope'] : []); const fragment_dependencies = new Set(this.slots.size ? ['$$scope'] : []);
this.slots.forEach(slot => { this.slots.forEach((slot) => {
slot.block.dependencies.forEach(name => { slot.block.dependencies.forEach((name) => {
const is_let = slot.scope.is_let(name); const is_let = slot.scope.is_let(name);
const variable = renderer.component.var_lookup.get(name); const variable = renderer.component.var_lookup.get(name);
@ -208,9 +225,14 @@ export default class InlineComponentWrapper extends Wrapper {
}); });
}); });
const dynamic_attributes = this.node.attributes.filter(a => a.get_dependencies().length > 0); const dynamic_attributes = this.node.attributes.filter((a) => a.get_dependencies().length > 0);
if (!uses_spread && (dynamic_attributes.length > 0 || this.node.bindings.length > 0 || fragment_dependencies.size > 0)) { if (
!uses_spread &&
(dynamic_attributes.length > 0 ||
this.node.bindings.length > 0 ||
fragment_dependencies.size > 0)
) {
updates.push(b`const ${name_changes} = {};`); updates.push(b`const ${name_changes} = {};`);
} }
@ -223,14 +245,15 @@ export default class InlineComponentWrapper extends Wrapper {
const all_dependencies: Set<string> = new Set(); const all_dependencies: Set<string> = new Set();
this.node.attributes.forEach(attr => { this.node.attributes.forEach((attr) => {
add_to_set(all_dependencies, attr.dependencies); add_to_set(all_dependencies, attr.dependencies);
}); });
this.node.attributes.forEach((attr, i) => { this.node.attributes.forEach((attr, i) => {
const { name, dependencies } = attr; const { name, dependencies } = attr;
const condition = dependencies.size > 0 && (dependencies.size !== all_dependencies.size) const condition =
dependencies.size > 0 && dependencies.size !== all_dependencies.size
? renderer.dirty(Array.from(dependencies)) ? renderer.dirty(Array.from(dependencies))
: null; : null;
const unchanged = dependencies.size === 0; const unchanged = dependencies.size === 0;
@ -306,7 +329,7 @@ export default class InlineComponentWrapper extends Wrapper {
}`); }`);
} }
const munged_bindings = this.node.bindings.map(binding => { const munged_bindings = this.node.bindings.map((binding) => {
component.has_reactive_assignments = true; component.has_reactive_assignments = true;
if (binding.name === 'this') { if (binding.name === 'this') {
@ -325,8 +348,7 @@ export default class InlineComponentWrapper extends Wrapper {
statements.push(b` statements.push(b`
if (${snippet} !== void 0) { if (${snippet} !== void 0) {
${props}.${binding.name} = ${snippet}; ${props}.${binding.name} = ${snippet};
}` }`);
);
updates.push(b` updates.push(b`
if (!${updating} && ${renderer.dirty(Array.from(binding.expression.dependencies))}) { if (!${updating} && ${renderer.dirty(Array.from(binding.expression.dependencies))}) {
@ -353,8 +375,7 @@ export default class InlineComponentWrapper extends Wrapper {
const params: Identifier[] = [x`#value` as Identifier]; const params: Identifier[] = [x`#value` as Identifier];
const args = [x`#value`]; const args = [x`#value`];
if (contextual_dependencies.length > 0) { if (contextual_dependencies.length > 0) {
contextual_dependencies.forEach((name) => {
contextual_dependencies.forEach(name => {
params.push({ params.push({
type: 'Identifier', type: 'Identifier',
name name
@ -364,7 +385,6 @@ export default class InlineComponentWrapper extends Wrapper {
args.push(renderer.reference(name)); args.push(renderer.reference(name));
}); });
block.maintain_context = true; // TODO put this somewhere more logical block.maintain_context = true; // TODO put this somewhere more logical
} }
@ -397,7 +417,7 @@ export default class InlineComponentWrapper extends Wrapper {
return b`@binding_callbacks.push(() => @bind(${this.var}, '${binding.name}', ${id}));`; return b`@binding_callbacks.push(() => @bind(${this.var}, '${binding.name}', ${id}));`;
}); });
const munged_handlers = this.node.handlers.map(handler => { const munged_handlers = this.node.handlers.map((handler) => {
const event_handler = new EventHandler(handler, this); const event_handler = new EventHandler(handler, this);
let snippet = event_handler.get_snippet(block); let snippet = event_handler.get_snippet(block);
if (handler.modifiers.has('once')) snippet = x`@once(${snippet})`; if (handler.modifiers.has('once')) snippet = x`@once(${snippet})`;
@ -405,8 +425,10 @@ export default class InlineComponentWrapper extends Wrapper {
return b`${name}.$on("${handler.name}", ${snippet});`; return b`${name}.$on("${handler.name}", ${snippet});`;
}); });
const mount_target = has_css_custom_properties ? css_custom_properties_wrapper : (parent_node || '#target'); const mount_target = has_css_custom_properties
const mount_anchor = has_css_custom_properties ? 'null' : (parent_node ? 'null' : '#anchor'); ? css_custom_properties_wrapper
: parent_node || '#target';
const mount_anchor = has_css_custom_properties ? 'null' : parent_node ? 'null' : '#anchor';
const to_claim = parent_nodes && this.renderer.options.hydratable; const to_claim = parent_nodes && this.renderer.options.hydratable;
let claim_nodes = parent_nodes; let claim_nodes = parent_nodes;
@ -418,15 +440,23 @@ export default class InlineComponentWrapper extends Wrapper {
const dependencies = this.node.expression.dynamic_dependencies(); const dependencies = this.node.expression.dynamic_dependencies();
if (has_css_custom_properties) { if (has_css_custom_properties) {
this.set_css_custom_properties(block, css_custom_properties_wrapper, css_custom_properties_wrapper_element, is_svg_namespace); this.set_css_custom_properties(
block,
css_custom_properties_wrapper,
css_custom_properties_wrapper_element,
is_svg_namespace
);
} }
block.chunks.init.push(b` block.chunks.init.push(b`
var ${switch_value} = ${snippet}; var ${switch_value} = ${snippet};
function ${switch_props}(#ctx) { function ${switch_props}(#ctx) {
${(this.node.attributes.length > 0 || this.node.bindings.length > 0) && b` ${
${props && b`let ${props} = ${attribute_object};`}`} (this.node.attributes.length > 0 || this.node.bindings.length > 0) &&
b`
${props && b`let ${props} = ${attribute_object};`}`
}
${statements} ${statements}
return ${component_opts}; return ${component_opts};
} }
@ -439,16 +469,30 @@ export default class InlineComponentWrapper extends Wrapper {
} }
`); `);
block.chunks.create.push( block.chunks.create.push(b`if (${name}) @create_component(${name}.$$.fragment);`);
b`if (${name}) @create_component(${name}.$$.fragment);`
);
if (css_custom_properties_wrapper) this.create_css_custom_properties_wrapper_mount_chunk(block, parent_node, css_custom_properties_wrapper); if (css_custom_properties_wrapper)
block.chunks.mount.push(b`if (${name}) @mount_component(${name}, ${mount_target}, ${mount_anchor});`); this.create_css_custom_properties_wrapper_mount_chunk(
block,
parent_node,
css_custom_properties_wrapper
);
block.chunks.mount.push(
b`if (${name}) @mount_component(${name}, ${mount_target}, ${mount_anchor});`
);
if (to_claim) { if (to_claim) {
if (css_custom_properties_wrapper) claim_nodes = this.create_css_custom_properties_wrapper_claim_chunk(block, claim_nodes, css_custom_properties_wrapper, css_custom_properties_wrapper_element, is_svg_namespace); if (css_custom_properties_wrapper)
block.chunks.claim.push(b`if (${name}) @claim_component(${name}.$$.fragment, ${claim_nodes});`); claim_nodes = this.create_css_custom_properties_wrapper_claim_chunk(
block,
claim_nodes,
css_custom_properties_wrapper,
css_custom_properties_wrapper_element,
is_svg_namespace
);
block.chunks.claim.push(
b`if (${name}) @claim_component(${name}.$$.fragment, ${claim_nodes});`
);
} }
if (updates.length) { if (updates.length) {
@ -459,7 +503,9 @@ export default class InlineComponentWrapper extends Wrapper {
const tmp_anchor = this.get_or_create_anchor(block, parent_node, parent_nodes); const tmp_anchor = this.get_or_create_anchor(block, parent_node, parent_nodes);
const anchor = has_css_custom_properties ? 'null' : tmp_anchor; const anchor = has_css_custom_properties ? 'null' : tmp_anchor;
const update_mount_node = has_css_custom_properties ? css_custom_properties_wrapper : this.get_update_mount_node(tmp_anchor); const update_mount_node = has_css_custom_properties
? css_custom_properties_wrapper
: this.get_update_mount_node(tmp_anchor);
const update_insert = const update_insert =
css_custom_properties_wrapper && css_custom_properties_wrapper &&
(tmp_anchor.name !== 'null' (tmp_anchor.name !== 'null'
@ -505,19 +551,23 @@ export default class InlineComponentWrapper extends Wrapper {
if (${name}) @transition_in(${name}.$$.fragment, #local); if (${name}) @transition_in(${name}.$$.fragment, #local);
`); `);
block.chunks.outro.push( block.chunks.outro.push(b`if (${name}) @transition_out(${name}.$$.fragment, #local);`);
b`if (${name}) @transition_out(${name}.$$.fragment, #local);`
);
block.chunks.destroy.push(b`if (${name}) @destroy_component(${name}, ${parent_node ? null : 'detaching'});`); block.chunks.destroy.push(
b`if (${name}) @destroy_component(${name}, ${parent_node ? null : 'detaching'});`
);
} else { } else {
const expression = this.node.name === 'svelte:self' const expression =
this.node.name === 'svelte:self'
? component.name ? component.name
: this.renderer.reference(string_to_member_expression(this.node.name)); : this.renderer.reference(string_to_member_expression(this.node.name));
block.chunks.init.push(b` block.chunks.init.push(b`
${(this.node.attributes.length > 0 || this.node.bindings.length > 0) && b` ${
${props && b`let ${props} = ${attribute_object};`}`} (this.node.attributes.length > 0 || this.node.bindings.length > 0) &&
b`
${props && b`let ${props} = ${attribute_object};`}`
}
${statements} ${statements}
${name} = new ${expression}(${component_opts}); ${name} = new ${expression}(${component_opts});
@ -526,15 +576,32 @@ export default class InlineComponentWrapper extends Wrapper {
`); `);
if (has_css_custom_properties) { if (has_css_custom_properties) {
this.set_css_custom_properties(block, css_custom_properties_wrapper, css_custom_properties_wrapper_element, is_svg_namespace); this.set_css_custom_properties(
block,
css_custom_properties_wrapper,
css_custom_properties_wrapper_element,
is_svg_namespace
);
} }
block.chunks.create.push(b`@create_component(${name}.$$.fragment);`); block.chunks.create.push(b`@create_component(${name}.$$.fragment);`);
if (css_custom_properties_wrapper) this.create_css_custom_properties_wrapper_mount_chunk(block, parent_node, css_custom_properties_wrapper); if (css_custom_properties_wrapper)
this.create_css_custom_properties_wrapper_mount_chunk(
block,
parent_node,
css_custom_properties_wrapper
);
block.chunks.mount.push(b`@mount_component(${name}, ${mount_target}, ${mount_anchor});`); block.chunks.mount.push(b`@mount_component(${name}, ${mount_target}, ${mount_anchor});`);
if (to_claim) { if (to_claim) {
if (css_custom_properties_wrapper) claim_nodes = this.create_css_custom_properties_wrapper_claim_chunk(block, claim_nodes, css_custom_properties_wrapper, css_custom_properties_wrapper_element, is_svg_namespace); if (css_custom_properties_wrapper)
claim_nodes = this.create_css_custom_properties_wrapper_claim_chunk(
block,
claim_nodes,
css_custom_properties_wrapper,
css_custom_properties_wrapper_element,
is_svg_namespace
);
block.chunks.claim.push(b`@claim_component(${name}.$$.fragment, ${claim_nodes});`); block.chunks.claim.push(b`@claim_component(${name}.$$.fragment, ${claim_nodes});`);
} }
@ -553,9 +620,7 @@ export default class InlineComponentWrapper extends Wrapper {
@destroy_component(${name}, ${parent_node ? null : 'detaching'}); @destroy_component(${name}, ${parent_node ? null : 'detaching'});
`); `);
block.chunks.outro.push( block.chunks.outro.push(b`@transition_out(${name}.$$.fragment, #local);`);
b`@transition_out(${name}.$$.fragment, #local);`
);
} }
} }
@ -573,7 +638,9 @@ export default class InlineComponentWrapper extends Wrapper {
block.chunks.mount.push(b`@insert(#target, ${css_custom_properties_wrapper}, #anchor);`); block.chunks.mount.push(b`@insert(#target, ${css_custom_properties_wrapper}, #anchor);`);
// TODO we eventually need to consider what happens to elements // TODO we eventually need to consider what happens to elements
// that belong to the same outgroup as an outroing element... // that belong to the same outgroup as an outroing element...
block.chunks.destroy.push(b`if (detaching && ${this.var}) @detach(${css_custom_properties_wrapper});`); block.chunks.destroy.push(
b`if (detaching && ${this.var}) @detach(${css_custom_properties_wrapper});`
);
} }
} }
@ -600,12 +667,21 @@ export default class InlineComponentWrapper extends Wrapper {
is_svg_namespace: boolean is_svg_namespace: boolean
) { ) {
const element = is_svg_namespace ? x`@svg_element` : x`@element`; const element = is_svg_namespace ? x`@svg_element` : x`@element`;
block.chunks.create.push(b`${css_custom_properties_wrapper} = ${element}("${css_custom_properties_wrapper_element}");`); block.chunks.create.push(
if (!is_svg_namespace) block.chunks.hydrate.push(b`@set_style(${css_custom_properties_wrapper}, "display", "contents");`); b`${css_custom_properties_wrapper} = ${element}("${css_custom_properties_wrapper_element}");`
);
if (!is_svg_namespace)
block.chunks.hydrate.push(
b`@set_style(${css_custom_properties_wrapper}, "display", "contents");`
);
this.node.css_custom_properties.forEach((attr) => { this.node.css_custom_properties.forEach((attr) => {
const dependencies = attr.get_dependencies(); const dependencies = attr.get_dependencies();
const should_cache = attr.should_cache(); const should_cache = attr.should_cache();
const last = should_cache && block.get_unique_name(`${attr.name.replace(regex_invalid_variable_identifier_characters, '_')}_last`); const last =
should_cache &&
block.get_unique_name(
`${attr.name.replace(regex_invalid_variable_identifier_characters, '_')}_last`
);
if (should_cache) block.add_variable(last); if (should_cache) block.add_variable(last);
const value = attr.get_value(block); const value = attr.get_value(block);
const init = should_cache ? x`${last} = ${value}` : value; const init = should_cache ? x`${last} = ${value}` : value;

@ -60,23 +60,21 @@ export default class KeyBlockWrapper extends Wrapper {
} }
render_dynamic_key(block: Block, parent_node: Identifier, parent_nodes: Identifier) { render_dynamic_key(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
this.fragment.render( this.fragment.render(this.block, null, x`#nodes` as unknown as Identifier);
this.block,
null,
(x`#nodes` as unknown) as Identifier
);
const has_transitions = !!( const has_transitions = !!(this.block.has_intro_method || this.block.has_outro_method);
this.block.has_intro_method || this.block.has_outro_method
);
const dynamic = this.block.has_update_method; const dynamic = this.block.has_update_method;
const previous_key = block.get_unique_name('previous_key'); const previous_key = block.get_unique_name('previous_key');
const snippet = this.node.expression.manipulate(block); const snippet = this.node.expression.manipulate(block);
block.add_variable(previous_key, snippet); block.add_variable(previous_key, snippet);
const not_equal = this.renderer.component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`; const not_equal = this.renderer.component.component_options.immutable
const condition = x`${this.renderer.dirty(this.dependencies)} && ${not_equal}(${previous_key}, ${previous_key} = ${snippet})`; ? x`@not_equal`
: x`@safe_not_equal`;
const condition = x`${this.renderer.dirty(
this.dependencies
)} && ${not_equal}(${previous_key}, ${previous_key} = ${snippet})`;
block.chunks.init.push(b` block.chunks.init.push(b`
let ${this.var} = ${this.block.name}(#ctx); let ${this.var} = ${this.block.name}(#ctx);
@ -86,9 +84,7 @@ export default class KeyBlockWrapper extends Wrapper {
block.chunks.claim.push(b`${this.var}.l(${parent_nodes});`); block.chunks.claim.push(b`${this.var}.l(${parent_nodes});`);
} }
block.chunks.mount.push( block.chunks.mount.push(
b`${this.var}.m(${parent_node || '#target'}, ${ b`${this.var}.m(${parent_node || '#target'}, ${parent_node ? 'null' : '#anchor'});`
parent_node ? 'null' : '#anchor'
});`
); );
const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes); const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes);
const body = b` const body = b`

@ -12,11 +12,21 @@ import AttributeWrapper from './Element/Attribute';
export default class MustacheTagWrapper extends Tag { export default class MustacheTagWrapper extends Tag {
var: Identifier = { type: 'Identifier', name: 't' }; var: Identifier = { type: 'Identifier', name: 't' };
constructor(renderer: Renderer, block: Block, parent: Wrapper, node: MustacheTag | RawMustacheTag) { constructor(
renderer: Renderer,
block: Block,
parent: Wrapper,
node: MustacheTag | RawMustacheTag
) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
} }
render(block: Block, parent_node: Identifier, parent_nodes: Identifier, data: Record<string, unknown> | undefined) { render(
block: Block,
parent_node: Identifier,
parent_nodes: Identifier,
data: Record<string, unknown> | undefined
) {
const contenteditable_attributes = const contenteditable_attributes =
this.parent instanceof ElementWrapper && this.parent instanceof ElementWrapper &&
this.parent.attributes.filter((a) => a.node.name === 'contenteditable'); this.parent.attributes.filter((a) => a.node.name === 'contenteditable');
@ -37,9 +47,7 @@ export default class MustacheTagWrapper extends Tag {
contenteditable_attr_value = x`${data.element_data_name}['contenteditable']`; contenteditable_attr_value = x`${data.element_data_name}['contenteditable']`;
} }
const { init } = this.rename_this_method( const { init } = this.rename_this_method(block, (value) => {
block,
value => {
if (contenteditable_attr_value) { if (contenteditable_attr_value) {
if (contenteditable_attr_value === true) { if (contenteditable_attr_value === true) {
return x`@set_data_contenteditable(${this.var}, ${value})`; return x`@set_data_contenteditable(${this.var}, ${value})`;
@ -49,8 +57,7 @@ export default class MustacheTagWrapper extends Tag {
} else { } else {
return x`@set_data(${this.var}, ${value})`; return x`@set_data(${this.var}, ${value})`;
} }
} });
);
block.add_element( block.add_element(
this.var, this.var,

@ -30,24 +30,20 @@ export default class RawMustacheTagWrapper extends Tag {
if (can_use_innerhtml) { if (can_use_innerhtml) {
const insert = (content: Node) => b`${parent_node}.innerHTML = ${content};`[0]; const insert = (content: Node) => b`${parent_node}.innerHTML = ${content};`[0];
const { init } = this.rename_this_method( const { init } = this.rename_this_method(block, (content) => insert(content));
block,
content => insert(content)
);
block.chunks.mount.push(insert(init)); block.chunks.mount.push(insert(init));
} else { } else {
const needs_anchor = in_head || (this.next ? !this.next.is_dom_node() : (!this.parent || !this.parent.is_dom_node())); const needs_anchor =
in_head ||
(this.next ? !this.next.is_dom_node() : !this.parent || !this.parent.is_dom_node());
const html_tag = block.get_unique_name('html_tag'); const html_tag = block.get_unique_name('html_tag');
const html_anchor = needs_anchor && block.get_unique_name('html_anchor'); const html_anchor = needs_anchor && block.get_unique_name('html_anchor');
block.add_variable(html_tag); block.add_variable(html_tag);
const { init } = this.rename_this_method( const { init } = this.rename_this_method(block, (content) => x`${html_tag}.p(${content})`);
block,
content => x`${html_tag}.p(${content})`
);
const update_anchor = needs_anchor ? html_anchor : this.next ? this.next.var : 'null'; const update_anchor = needs_anchor ? html_anchor : this.next ? this.next.var : 'null';
@ -56,10 +52,14 @@ export default class RawMustacheTagWrapper extends Tag {
block.chunks.create.push(b`${html_tag} = new @HtmlTag(${is_svg ? 'true' : 'false'});`); block.chunks.create.push(b`${html_tag} = new @HtmlTag(${is_svg ? 'true' : 'false'});`);
if (this.renderer.options.hydratable) { if (this.renderer.options.hydratable) {
block.chunks.claim.push(b`${html_tag} = @claim_html_tag(${_parent_nodes}, ${is_svg ? 'true' : 'false'});`); block.chunks.claim.push(
b`${html_tag} = @claim_html_tag(${_parent_nodes}, ${is_svg ? 'true' : 'false'});`
);
} }
block.chunks.hydrate.push(b`${html_tag}.a = ${update_anchor};`); block.chunks.hydrate.push(b`${html_tag}.a = ${update_anchor};`);
block.chunks.mount.push(b`${html_tag}.m(${init}, ${parent_node || '#target'}, ${parent_node ? null : '#anchor'});`); block.chunks.mount.push(
b`${html_tag}.m(${init}, ${parent_node || '#target'}, ${parent_node ? null : '#anchor'});`
);
if (needs_anchor) { if (needs_anchor) {
block.add_element(html_anchor, x`@empty()`, x`@empty()`, parent_node); block.add_element(html_anchor, x`@empty()`, x`@empty()`, parent_node);

@ -49,7 +49,7 @@ export default class SlotWrapper extends Wrapper {
next_sibling next_sibling
); );
this.node.values.forEach(attribute => { this.node.values.forEach((attribute) => {
add_to_set(this.dependencies, attribute.dependencies); add_to_set(this.dependencies, attribute.dependencies);
}); });
@ -60,11 +60,7 @@ export default class SlotWrapper extends Wrapper {
block.add_outro(); block.add_outro();
} }
render( render(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
block: Block,
parent_node: Identifier,
parent_nodes: Identifier
) {
const { renderer } = this; const { renderer } = this;
const { slot_name } = this.node; const { slot_name } = this.node;
@ -78,18 +74,27 @@ export default class SlotWrapper extends Wrapper {
let get_slot_context_fn: Identifier | 'null'; let get_slot_context_fn: Identifier | 'null';
if (this.node.values.size > 0) { if (this.node.values.size > 0) {
get_slot_changes_fn = renderer.component.get_unique_name(`get_${sanitize(slot_name)}_slot_changes`); get_slot_changes_fn = renderer.component.get_unique_name(
get_slot_context_fn = renderer.component.get_unique_name(`get_${sanitize(slot_name)}_slot_context`); `get_${sanitize(slot_name)}_slot_changes`
);
get_slot_context_fn = renderer.component.get_unique_name(
`get_${sanitize(slot_name)}_slot_context`
);
const changes = x`{}` as ObjectExpression; const changes = x`{}` as ObjectExpression;
const spread_dynamic_dependencies = new Set<string>(); const spread_dynamic_dependencies = new Set<string>();
this.node.values.forEach(attribute => { this.node.values.forEach((attribute) => {
if (attribute.type === 'Spread') { if (attribute.type === 'Spread') {
add_to_set(spread_dynamic_dependencies, Array.from(attribute.dependencies).filter((name) => this.is_dependency_dynamic(name))); add_to_set(
spread_dynamic_dependencies,
Array.from(attribute.dependencies).filter((name) => this.is_dependency_dynamic(name))
);
} else { } else {
const dynamic_dependencies = Array.from(attribute.dependencies).filter((name) => this.is_dependency_dynamic(name)); const dynamic_dependencies = Array.from(attribute.dependencies).filter((name) =>
this.is_dependency_dynamic(name)
);
if (dynamic_dependencies.length > 0) { if (dynamic_dependencies.length > 0) {
changes.properties.push(p`${attribute.name}: ${renderer.dirty(dynamic_dependencies)}`); changes.properties.push(p`${attribute.name}: ${renderer.dirty(dynamic_dependencies)}`);
@ -103,9 +108,13 @@ export default class SlotWrapper extends Wrapper {
`); `);
if (spread_dynamic_dependencies.size) { if (spread_dynamic_dependencies.size) {
get_slot_spread_changes_fn = renderer.component.get_unique_name(`get_${sanitize(slot_name)}_slot_spread_changes`); get_slot_spread_changes_fn = renderer.component.get_unique_name(
`get_${sanitize(slot_name)}_slot_spread_changes`
);
renderer.blocks.push(b` renderer.blocks.push(b`
const ${get_slot_spread_changes_fn} = #dirty => ${renderer.dirty(Array.from(spread_dynamic_dependencies))}; const ${get_slot_spread_changes_fn} = #dirty => ${renderer.dirty(
Array.from(spread_dynamic_dependencies)
)};
`); `);
} }
} else { } else {
@ -124,22 +133,22 @@ export default class SlotWrapper extends Wrapper {
const slot = block.get_unique_name(`${sanitize(slot_name)}_slot`); const slot = block.get_unique_name(`${sanitize(slot_name)}_slot`);
const slot_definition = block.get_unique_name(`${sanitize(slot_name)}_slot_template`); const slot_definition = block.get_unique_name(`${sanitize(slot_name)}_slot_template`);
const slot_or_fallback = has_fallback ? block.get_unique_name(`${sanitize(slot_name)}_slot_or_fallback`) : slot; const slot_or_fallback = has_fallback
? block.get_unique_name(`${sanitize(slot_name)}_slot_or_fallback`)
: slot;
block.chunks.init.push(b` block.chunks.init.push(b`
const ${slot_definition} = ${renderer.reference('#slots')}.${slot_name}; const ${slot_definition} = ${renderer.reference('#slots')}.${slot_name};
const ${slot} = @create_slot(${slot_definition}, #ctx, ${renderer.reference('$$scope')}, ${get_slot_context_fn}); const ${slot} = @create_slot(${slot_definition}, #ctx, ${renderer.reference(
'$$scope'
)}, ${get_slot_context_fn});
${has_fallback ? b`const ${slot_or_fallback} = ${slot} || ${this.fallback.name}(#ctx);` : null} ${has_fallback ? b`const ${slot_or_fallback} = ${slot} || ${this.fallback.name}(#ctx);` : null}
`); `);
block.chunks.create.push( block.chunks.create.push(b`if (${slot_or_fallback}) ${slot_or_fallback}.c();`);
b`if (${slot_or_fallback}) ${slot_or_fallback}.c();`
);
if (renderer.options.hydratable) { if (renderer.options.hydratable) {
block.chunks.claim.push( block.chunks.claim.push(b`if (${slot_or_fallback}) ${slot_or_fallback}.l(${parent_nodes});`);
b`if (${slot_or_fallback}) ${slot_or_fallback}.l(${parent_nodes});`
);
} }
block.chunks.mount.push(b` block.chunks.mount.push(b`
@ -148,15 +157,13 @@ export default class SlotWrapper extends Wrapper {
} }
`); `);
block.chunks.intro.push( block.chunks.intro.push(b`@transition_in(${slot_or_fallback}, #local);`);
b`@transition_in(${slot_or_fallback}, #local);`
);
block.chunks.outro.push( block.chunks.outro.push(b`@transition_out(${slot_or_fallback}, #local);`);
b`@transition_out(${slot_or_fallback}, #local);`
);
const dynamic_dependencies = Array.from(this.dependencies).filter((name) => this.is_dependency_dynamic(name)); const dynamic_dependencies = Array.from(this.dependencies).filter((name) =>
this.is_dependency_dynamic(name)
);
const fallback_dynamic_dependencies = has_fallback const fallback_dynamic_dependencies = has_fallback
? Array.from(this.fallback.dependencies).filter((name) => this.is_dependency_dynamic(name)) ? Array.from(this.fallback.dependencies).filter((name) => this.is_dependency_dynamic(name))
@ -172,21 +179,31 @@ export default class SlotWrapper extends Wrapper {
get_slot_spread_changes_fn ? x`${get_slot_spread_changes_fn}(#dirty)` : null, get_slot_spread_changes_fn ? x`${get_slot_spread_changes_fn}(#dirty)` : null,
block.has_outros ? x`!#current` : null block.has_outros ? x`!#current` : null
].filter(Boolean); ].filter(Boolean);
const all_dirty_condition = all_dirty_conditions.length ? all_dirty_conditions.reduce((condition1, condition2) => x`${condition1} || ${condition2}`) : null; const all_dirty_condition = all_dirty_conditions.length
? all_dirty_conditions.reduce((condition1, condition2) => x`${condition1} || ${condition2}`)
: null;
let slot_update: Node[]; let slot_update: Node[];
if (all_dirty_condition) { if (all_dirty_condition) {
const dirty = x`${all_dirty_condition} ? @get_all_dirty_from_scope(${renderer.reference('$$scope')}) : @get_slot_changes(${slot_definition}, ${renderer.reference('$$scope')}, #dirty, ${get_slot_changes_fn})`; const dirty = x`${all_dirty_condition} ? @get_all_dirty_from_scope(${renderer.reference(
'$$scope'
)}) : @get_slot_changes(${slot_definition}, ${renderer.reference(
'$$scope'
)}, #dirty, ${get_slot_changes_fn})`;
slot_update = b` slot_update = b`
if (${slot}.p && ${condition}) { if (${slot}.p && ${condition}) {
@update_slot_base(${slot}, ${slot_definition}, #ctx, ${renderer.reference('$$scope')}, ${dirty}, ${get_slot_context_fn}); @update_slot_base(${slot}, ${slot_definition}, #ctx, ${renderer.reference(
'$$scope'
)}, ${dirty}, ${get_slot_context_fn});
} }
`; `;
} else { } else {
slot_update = b` slot_update = b`
if (${slot}.p && ${condition}) { if (${slot}.p && ${condition}) {
@update_slot(${slot}, ${slot_definition}, #ctx, ${renderer.reference('$$scope')}, #dirty, ${get_slot_changes_fn}, ${get_slot_context_fn}); @update_slot(${slot}, ${slot_definition}, #ctx, ${renderer.reference(
'$$scope'
)}, #dirty, ${get_slot_changes_fn}, ${get_slot_context_fn});
} }
`; `;
} }
@ -198,7 +215,10 @@ export default class SlotWrapper extends Wrapper {
fallback_dirty = x`!#current ? ${renderer.get_initial_dirty()} : ${fallback_dirty}`; fallback_dirty = x`!#current ? ${renderer.get_initial_dirty()} : ${fallback_dirty}`;
} }
const fallback_update = has_fallback && fallback_dynamic_dependencies.length > 0 && b` const fallback_update =
has_fallback &&
fallback_dynamic_dependencies.length > 0 &&
b`
if (${slot_or_fallback} && ${slot_or_fallback}.p && ${fallback_condition}) { if (${slot_or_fallback} && ${slot_or_fallback}.p && ${fallback_condition}) {
${slot_or_fallback}.p(#ctx, ${fallback_dirty}); ${slot_or_fallback}.p(#ctx, ${fallback_dirty});
} }
@ -220,9 +240,7 @@ export default class SlotWrapper extends Wrapper {
`); `);
} }
block.chunks.destroy.push( block.chunks.destroy.push(b`if (${slot_or_fallback}) ${slot_or_fallback}.d(detaching);`);
b`if (${slot_or_fallback}) ${slot_or_fallback}.d(detaching);`
);
} }
is_dependency_dynamic(name: string) { is_dependency_dynamic(name: string) {

@ -30,8 +30,8 @@ export default class SlotTemplateWrapper extends Wrapper {
const { scope, lets, const_tags, slot_template_name } = this.node; const { scope, lets, const_tags, slot_template_name } = this.node;
lets.forEach(l => { lets.forEach((l) => {
extract_names(l.value || l.name).forEach(name => { extract_names(l.value || l.name).forEach((name) => {
renderer.add_to_context(name, true); renderer.add_to_context(name, true);
}); });
}); });
@ -40,22 +40,17 @@ export default class SlotTemplateWrapper extends Wrapper {
this.block = block.child({ this.block = block.child({
comment: create_debugging_comment(this.node, this.renderer.component), comment: create_debugging_comment(this.node, this.renderer.component),
name: this.renderer.component.get_unique_name( name: this.renderer.component.get_unique_name(`create_${sanitize(slot_template_name)}_slot`),
`create_${sanitize(slot_template_name)}_slot`
),
type: 'slot' type: 'slot'
}); });
this.renderer.blocks.push(this.block); this.renderer.blocks.push(this.block);
const seen = new Set(lets.map(l => l.name.name)); const seen = new Set(lets.map((l) => l.name.name));
this.parent.node.lets.forEach(l => { this.parent.node.lets.forEach((l) => {
if (!seen.has(l.name.name)) lets.push(l); if (!seen.has(l.name.name)) lets.push(l);
}); });
this.parent.set_slot( this.parent.set_slot(slot_template_name, get_slot_definition(this.block, scope, lets));
slot_template_name,
get_slot_definition(this.block, scope, lets)
);
this.fragment = new FragmentWrapper( this.fragment = new FragmentWrapper(
renderer, renderer,

@ -11,13 +11,7 @@ export default class TextWrapper extends Wrapper {
skip: boolean; skip: boolean;
var: Identifier; var: Identifier;
constructor( constructor(renderer: Renderer, block: Block, parent: Wrapper, node: Text, data: string) {
renderer: Renderer,
block: Block,
parent: Wrapper,
node: Text,
data: string
) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.skip = this.node.should_skip(); this.skip = this.node.should_skip();
@ -55,7 +49,10 @@ export default class TextWrapper extends Wrapper {
block.add_element( block.add_element(
this.var, this.var,
use_space ? x`@space()` : x`@text(${string_literal})`, use_space ? x`@space()` : x`@text(${string_literal})`,
parent_nodes && (use_space ? x`@claim_space(${parent_nodes})` : x`@claim_text(${parent_nodes}, ${string_literal})`), parent_nodes &&
(use_space
? x`@claim_space(${parent_nodes})`
: x`@claim_text(${parent_nodes}, ${string_literal})`),
parent_node as Identifier parent_node as Identifier
); );
} }

@ -24,7 +24,7 @@ export default class TitleWrapper extends Wrapper {
} }
render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) { render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) {
const is_dynamic = !!this.node.children.find(node => node.type !== 'Text'); const is_dynamic = !!this.node.children.find((node) => node.type !== 'Text');
if (is_dynamic) { if (is_dynamic) {
let value; let value;
@ -42,10 +42,10 @@ export default class TitleWrapper extends Wrapper {
} else { } else {
// '{foo} {bar}' — treat as string concatenation // '{foo} {bar}' — treat as string concatenation
value = this.node.children value = this.node.children
.map(chunk => { .map((chunk) => {
if (chunk.type === 'Text') return string_literal(chunk.data); if (chunk.type === 'Text') return string_literal(chunk.data);
(chunk as MustacheTag).expression.dependencies.forEach(d => { (chunk as MustacheTag).expression.dependencies.forEach((d) => {
all_dependencies.add(d); all_dependencies.add(d);
}); });
@ -58,17 +58,13 @@ export default class TitleWrapper extends Wrapper {
} }
} }
const last = this.node.should_cache && block.get_unique_name( const last = this.node.should_cache && block.get_unique_name('title_value');
'title_value'
);
if (this.node.should_cache) block.add_variable(last); if (this.node.should_cache) block.add_variable(last);
const init = this.node.should_cache ? x`${last} = ${value}` : value; const init = this.node.should_cache ? x`${last} = ${value}` : value;
block.chunks.init.push( block.chunks.init.push(b`@_document.title = ${init};`);
b`@_document.title = ${init};`
);
const updater = b`@_document.title = ${this.node.should_cache ? last : value};`; const updater = b`@_document.title = ${this.node.should_cache ? last : value};`;
@ -91,7 +87,8 @@ export default class TitleWrapper extends Wrapper {
}`); }`);
} }
} else { } else {
const value = this.node.children.length > 0 const value =
this.node.children.length > 0
? string_literal((this.node.children[0] as Text).data) ? string_literal((this.node.children[0] as Text).data)
: x`""`; : x`""`;

@ -40,7 +40,7 @@ export default class WindowWrapper extends Wrapper {
constructor(renderer: Renderer, block: Block, parent: Wrapper, node: TemplateNode) { constructor(renderer: Renderer, block: Block, parent: Wrapper, node: TemplateNode) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.handlers = this.node.handlers.map(handler => new EventHandler(handler, this)); this.handlers = this.node.handlers.map((handler) => new EventHandler(handler, this));
} }
render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) { render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) {
@ -53,7 +53,7 @@ export default class WindowWrapper extends Wrapper {
add_actions(block, '@_window', this.node.actions); add_actions(block, '@_window', this.node.actions);
add_event_handlers(block, '@_window', this.handlers); add_event_handlers(block, '@_window', this.handlers);
this.node.bindings.forEach(binding => { this.node.bindings.forEach((binding) => {
// TODO: what if it's a MemberExpression? // TODO: what if it's a MemberExpression?
const binding_name = (binding.expression.node as Identifier).name; const binding_name = (binding.expression.node as Identifier).name;
@ -81,7 +81,7 @@ export default class WindowWrapper extends Wrapper {
const clear_scrolling = block.get_unique_name('clear_scrolling'); const clear_scrolling = block.get_unique_name('clear_scrolling');
const scrolling_timeout = block.get_unique_name('scrolling_timeout'); const scrolling_timeout = block.get_unique_name('scrolling_timeout');
Object.keys(events).forEach(event => { Object.keys(events).forEach((event) => {
const id = block.get_unique_name(`onwindow${event}`); const id = block.get_unique_name(`onwindow${event}`);
const props = events[event]; const props = events[event];
@ -94,7 +94,8 @@ export default class WindowWrapper extends Wrapper {
block.add_variable(clear_scrolling, x`() => { ${scrolling} = false }`); block.add_variable(clear_scrolling, x`() => { ${scrolling} = false }`);
block.add_variable(scrolling_timeout); block.add_variable(scrolling_timeout);
const condition = bindings.scrollX && bindings.scrollY const condition =
bindings.scrollX && bindings.scrollY
? x`"${bindings.scrollX}" in this._state || "${bindings.scrollY}" in this._state` ? x`"${bindings.scrollX}" in this._state || "${bindings.scrollY}" in this._state`
: x`"${bindings.scrollX || bindings.scrollY}" in this._state`; : x`"${bindings.scrollX || bindings.scrollY}" in this._state`;
@ -118,10 +119,8 @@ export default class WindowWrapper extends Wrapper {
}) })
`); `);
} else { } else {
props.forEach(prop => { props.forEach((prop) => {
renderer.meta_bindings.push( renderer.meta_bindings.push(b`this._state.${prop.name} = @_window.${prop.value};`);
b`this._state.${prop.name} = @_window.${prop.value};`
);
}); });
block.event_listeners.push(x` block.event_listeners.push(x`
@ -131,7 +130,7 @@ export default class WindowWrapper extends Wrapper {
component.partly_hoisted.push(b` component.partly_hoisted.push(b`
function ${id}() { function ${id}() {
${props.map(prop => renderer.invalidate(prop.name, x`${prop.name} = @_window.${prop.value}`))} ${props.map((prop) => renderer.invalidate(prop.name, x`${prop.name} = @_window.${prop.value}`))}
} }
`); `);
@ -146,8 +145,12 @@ export default class WindowWrapper extends Wrapper {
if (bindings.scrollX || bindings.scrollY) { if (bindings.scrollX || bindings.scrollY) {
const condition = renderer.dirty([bindings.scrollX, bindings.scrollY].filter(Boolean)); const condition = renderer.dirty([bindings.scrollX, bindings.scrollY].filter(Boolean));
const scrollX = bindings.scrollX ? renderer.reference(bindings.scrollX) : x`@_window.pageXOffset`; const scrollX = bindings.scrollX
const scrollY = bindings.scrollY ? renderer.reference(bindings.scrollY) : x`@_window.pageYOffset`; ? renderer.reference(bindings.scrollX)
: x`@_window.pageXOffset`;
const scrollY = bindings.scrollY
? renderer.reference(bindings.scrollY)
: x`@_window.pageYOffset`;
block.chunks.update.push(b` block.chunks.update.push(b`
if (${condition} && !${scrolling}) { if (${condition} && !${scrolling}) {

@ -9,16 +9,18 @@ import { Node } from 'estree';
export default class Tag extends Wrapper { export default class Tag extends Wrapper {
node: MustacheTag | RawMustacheTag; node: MustacheTag | RawMustacheTag;
constructor(renderer: Renderer, block: Block, parent: Wrapper, node: MustacheTag | RawMustacheTag) { constructor(
renderer: Renderer,
block: Block,
parent: Wrapper,
node: MustacheTag | RawMustacheTag
) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
block.add_dependencies(node.expression.dependencies); block.add_dependencies(node.expression.dependencies);
} }
rename_this_method( rename_this_method(block: Block, update: (value: Node) => Node | Node[]) {
block: Block,
update: ((value: Node) => (Node | Node[]))
) {
const dependencies = this.node.expression.dynamic_dependencies(); const dependencies = this.node.expression.dynamic_dependencies();
let snippet = this.node.expression.manipulate(block); let snippet = this.node.expression.manipulate(block);

@ -14,12 +14,7 @@ export default class Wrapper {
var: Identifier; var: Identifier;
constructor( constructor(renderer: Renderer, block: Block, parent: Wrapper, node: TemplateNode) {
renderer: Renderer,
block: Block,
parent: Wrapper,
node: TemplateNode
) {
this.node = node; this.node = node;
// make these non-enumerable so that they can be logged sensibly // make these non-enumerable so that they can be logged sensibly
@ -39,7 +34,9 @@ export default class Wrapper {
get_or_create_anchor(block: Block, parent_node: Identifier, parent_nodes: Identifier) { get_or_create_anchor(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
// TODO use this in EachBlock and IfBlock — tricky because // TODO use this in EachBlock and IfBlock — tricky because
// children need to be created first // children need to be created first
const needs_anchor = this.next ? !this.next.is_dom_node() : !parent_node || !this.parent.is_dom_node(); const needs_anchor = this.next
? !this.next.is_dom_node()
: !parent_node || !this.parent.is_dom_node();
const anchor = needs_anchor const anchor = needs_anchor
? block.get_unique_name(`${this.var.name}_anchor`) ? block.get_unique_name(`${this.var.name}_anchor`)
: (this.next && this.next.var) || { type: 'Identifier', name: 'null' }; : (this.next && this.next.var) || { type: 'Identifier', name: 'null' };
@ -57,20 +54,23 @@ export default class Wrapper {
} }
get_update_mount_node(anchor: Identifier): Identifier { get_update_mount_node(anchor: Identifier): Identifier {
return ((this.parent && this.parent.is_dom_node()) return (
? this.parent.var this.parent && this.parent.is_dom_node() ? this.parent.var : x`${anchor}.parentNode`
: x`${anchor}.parentNode`) as Identifier; ) as Identifier;
} }
is_dom_node() { is_dom_node() {
return ( return (
this.node.type === 'Element' || this.node.type === 'Element' || this.node.type === 'Text' || this.node.type === 'MustacheTag'
this.node.type === 'Text' ||
this.node.type === 'MustacheTag'
); );
} }
render(_block: Block, _parent_node: Identifier, _parent_nodes: Identifier, _data: Record<string, any> = undefined) { render(
_block: Block,
_parent_node: Identifier,
_parent_nodes: Identifier,
_data: Record<string, any> = undefined
) {
throw Error('Wrapper class is not renderable'); throw Error('Wrapper class is not renderable');
} }
} }

@ -4,12 +4,8 @@ import Action from '../../../nodes/Action';
import { Expression, Node } from 'estree'; import { Expression, Node } from 'estree';
import is_contextual from '../../../nodes/shared/is_contextual'; import is_contextual from '../../../nodes/shared/is_contextual';
export default function add_actions( export default function add_actions(block: Block, target: string | Expression, actions: Action[]) {
block: Block, actions.forEach((action) => add_action(block, target, action));
target: string | Expression,
actions: Action[]
) {
actions.forEach(action => add_action(block, target, action));
} }
const regex_invalid_variable_identifier_characters = /[^a-zA-Z0-9_$]/g; const regex_invalid_variable_identifier_characters = /[^a-zA-Z0-9_$]/g;
@ -54,8 +50,6 @@ export function add_action(block: Block, target: string | Expression, action: Ac
condition = x`${condition} && ${block.renderer.dirty(dependencies)}`; condition = x`${condition} && ${block.renderer.dirty(dependencies)}`;
} }
block.chunks.update.push( block.chunks.update.push(b`if (${condition}) ${id}.update.call(null, ${snippet});`);
b`if (${condition}) ${id}.update.call(null, ${snippet});`
);
} }
} }

@ -9,14 +9,28 @@ export function add_const_tags(block: Block, const_tags: ConstTag[], ctx: string
const_tags.forEach((const_tag, i) => { const_tags.forEach((const_tag, i) => {
const name = `#constants_${i}`; const name = `#constants_${i}`;
const_tags_props.push(b`const ${name} = ${const_tag.expression.manipulate(block, ctx)}`); const_tags_props.push(b`const ${name} = ${const_tag.expression.manipulate(block, ctx)}`);
const to_ctx = (name: string) => block.renderer.context_lookup.has(name) ? x`${ctx}[${block.renderer.context_lookup.get(name).index}]` : { type: 'Identifier', name } as Node; const to_ctx = (name: string) =>
block.renderer.context_lookup.has(name)
? x`${ctx}[${block.renderer.context_lookup.get(name).index}]`
: ({ type: 'Identifier', name } as Node);
const_tag.contexts.forEach(context => { const_tag.contexts.forEach((context) => {
if (context.type === 'DestructuredVariable') { if (context.type === 'DestructuredVariable') {
const_tags_props.push(b`${ctx}[${block.renderer.context_lookup.get(context.key.name).index}] = ${context.default_modifier(context.modifier({ type: 'Identifier', name }), to_ctx)}`); const_tags_props.push(
b`${ctx}[${
block.renderer.context_lookup.get(context.key.name).index
}] = ${context.default_modifier(context.modifier({ type: 'Identifier', name }), to_ctx)}`
);
} else { } else {
const expression = new Expression(block.renderer.component, const_tag, const_tag.scope, context.key); const expression = new Expression(
const_tags_props.push(b`const ${context.property_name} = ${expression.manipulate(block, ctx)}`); block.renderer.component,
const_tag,
const_tag.scope,
context.key
);
const_tags_props.push(
b`const ${context.property_name} = ${expression.manipulate(block, ctx)}`
);
} }
}); });
}); });
@ -24,8 +38,8 @@ export function add_const_tags(block: Block, const_tags: ConstTag[], ctx: string
} }
export function add_const_tags_context(renderer: Renderer, const_tags: ConstTag[]) { export function add_const_tags_context(renderer: Renderer, const_tags: ConstTag[]) {
const_tags.forEach(const_tag => { const_tags.forEach((const_tag) => {
const_tag.contexts.forEach(context => { const_tag.contexts.forEach((context) => {
if (context.type !== 'DestructuredVariable') return; if (context.type !== 'DestructuredVariable') return;
renderer.add_to_context(context.key.name, true); renderer.add_to_context(context.key.name, true);
}); });

@ -7,7 +7,7 @@ export default function add_event_handlers(
target: string | Expression, target: string | Expression,
handlers: EventHandler[] handlers: EventHandler[]
) { ) {
handlers.forEach(handler => add_event_handler(block, target, handler)); handlers.forEach((handler) => add_event_handler(block, target, handler));
} }
export function add_event_handler( export function add_event_handler(

@ -5,7 +5,12 @@ import BindingWrapper from '../Element/Binding';
import { Identifier } from 'estree'; import { Identifier } from 'estree';
import { compare_node } from '../../../utils/compare_node'; import { compare_node } from '../../../utils/compare_node';
export default function bind_this(component: Component, block: Block, binding: BindingWrapper, variable: Identifier) { export default function bind_this(
component: Component,
block: Block,
binding: BindingWrapper,
variable: Identifier
) {
const fn = component.get_unique_name(`${variable.name}_binding`); const fn = component.get_unique_name(`${variable.name}_binding`);
block.renderer.add_to_context(fn.name); block.renderer.add_to_context(fn.name);
@ -17,13 +22,13 @@ export default function bind_this(component: Component, block: Block, binding: B
const body = b` const body = b`
${mutation} ${mutation}
${Array.from(dependencies) ${Array.from(dependencies)
.filter(dep => dep[0] !== '$') .filter((dep) => dep[0] !== '$')
.filter(dep => !contextual_dependencies.has(dep)) .filter((dep) => !contextual_dependencies.has(dep))
.map(dep => b`${block.renderer.invalidate(dep)};`)} .map((dep) => b`${block.renderer.invalidate(dep)};`)}
`; `;
if (contextual_dependencies.size) { if (contextual_dependencies.size) {
const params: Identifier[] = Array.from(contextual_dependencies).map(name => ({ const params: Identifier[] = Array.from(contextual_dependencies).map((name) => ({
type: 'Identifier', type: 'Identifier',
name name
})); }));
@ -66,7 +71,9 @@ export default function bind_this(component: Component, block: Block, binding: B
`); `);
const condition = Array.from(args) const condition = Array.from(args)
.map(name => x`${name} !== ${block.renderer.reference(alias_map.get(name.name) || name.name)}`) .map(
(name) => x`${name} !== ${block.renderer.reference(alias_map.get(name.name) || name.name)}`
)
.reduce((lhs, rhs) => x`${lhs} || ${rhs}`); .reduce((lhs, rhs) => x`${lhs} || ${rhs}`);
// we push unassign and unshift assign so that references are // we push unassign and unshift assign so that references are
@ -75,10 +82,9 @@ export default function bind_this(component: Component, block: Block, binding: B
block.chunks.update.push(b` block.chunks.update.push(b`
if (${condition}) { if (${condition}) {
${unassign}(); ${unassign}();
${args.map(a => b`${a} = ${block.renderer.reference(alias_map.get(a.name) || a.name)}`)}; ${args.map((a) => b`${a} = ${block.renderer.reference(alias_map.get(a.name) || a.name)}`)};
${assign}(); ${assign}();
}` }`);
);
block.chunks.destroy.push(b`${unassign}();`); block.chunks.destroy.push(b`${unassign}();`);
return b`${assign}();`; return b`${assign}();`;

@ -2,11 +2,7 @@ import Component from '../../../Component';
import { INode } from '../../../nodes/interfaces'; import { INode } from '../../../nodes/interfaces';
import { regex_whitespace_characters } from '../../../../utils/patterns'; import { regex_whitespace_characters } from '../../../../utils/patterns';
export default function create_debugging_comment(node: INode, component: Component) {
export default function create_debugging_comment(
node: INode,
component: Component
) {
const { locate, source } = component; const { locate, source } = component;
let c = node.start; let c = node.start;

@ -9,7 +9,7 @@ export function get_slot_definition(block: Block, scope: TemplateScope, lets: Le
const context_input = { const context_input = {
type: 'ObjectPattern', type: 'ObjectPattern',
properties: lets.map(l => ({ properties: lets.map((l) => ({
type: 'Property', type: 'Property',
kind: 'init', kind: 'init',
key: l.name, key: l.name,
@ -20,7 +20,7 @@ export function get_slot_definition(block: Block, scope: TemplateScope, lets: Le
const properties = []; const properties = [];
const value_map = new Map(); const value_map = new Map();
lets.forEach(l => { lets.forEach((l) => {
let value: Identifier; let value: Identifier;
if (l.names.length > 1) { if (l.names.length > 1) {
// more than one, probably destructuring // more than one, probably destructuring
@ -46,8 +46,8 @@ export function get_slot_definition(block: Block, scope: TemplateScope, lets: Le
const names: Set<string> = new Set(); const names: Set<string> = new Set();
const names_lookup: Map<string, string> = new Map(); const names_lookup: Map<string, string> = new Map();
lets.forEach(l => { lets.forEach((l) => {
l.names.forEach(name => { l.names.forEach((name) => {
names.add(name); names.add(name);
if (value_map.has(l.value)) { if (value_map.has(l.value)) {
names_lookup.set(name, value_map.get(l.value)); names_lookup.set(name, value_map.get(l.value));
@ -57,7 +57,9 @@ export function get_slot_definition(block: Block, scope: TemplateScope, lets: Le
const context = { const context = {
type: 'ObjectExpression', type: 'ObjectExpression',
properties: Array.from(names).map(name => p`${block.renderer.context_lookup.get(name).index}: ${name}`) properties: Array.from(names).map(
(name) => p`${block.renderer.context_lookup.get(name).index}: ${name}`
)
}; };
const { context_lookup } = block.renderer; const { context_lookup } = block.renderer;
@ -70,7 +72,7 @@ export function get_slot_definition(block: Block, scope: TemplateScope, lets: Le
if (block.renderer.context_overflow) { if (block.renderer.context_overflow) {
const grouped = []; const grouped = [];
Array.from(names).forEach(name => { Array.from(names).forEach((name) => {
const i = context_lookup.get(name).index.value as number; const i = context_lookup.get(name).index.value as number;
const g = Math.floor(i / 31); const g = Math.floor(i / 31);
@ -86,7 +88,9 @@ export function get_slot_definition(block: Block, scope: TemplateScope, lets: Le
elements[g] = grouped[g] elements[g] = grouped[g]
? grouped[g] ? grouped[g]
.map(({ name, n }) => x`${name} ? ${1 << n} : 0`) .map(({ name, n }) => x`${name} ? ${1 << n} : 0`)
.reduce((lhs: ReturnType<typeof x>, rhs: ReturnType<typeof x>) => x`${lhs} | ${rhs}`) .reduce(
(lhs: ReturnType<typeof x>, rhs: ReturnType<typeof x>) => x`${lhs} | ${rhs}`
)
: x`0`; : x`0`;
} }
@ -97,7 +101,7 @@ export function get_slot_definition(block: Block, scope: TemplateScope, lets: Le
} }
return Array.from(names) return Array.from(names)
.map(name => { .map((name) => {
const lookup_name = names_lookup.has(name) ? names_lookup.get(name) : name; const lookup_name = names_lookup.has(name) ? names_lookup.get(name) : name;
const i = context_lookup.get(name).index.value as number; const i = context_lookup.get(name).index.value as number;
return x`${lookup_name} ? ${1 << i} : 0`; return x`${lookup_name} ? ${1 << i} : 0`;

@ -1,5 +1,10 @@
import { Node } from 'estree'; import { Node } from 'estree';
export function is_head(node: Node) { export function is_head(node: Node) {
return node && node.type === 'MemberExpression' && node.object['name'] === '@_document' && node.property['name'] === 'head'; return (
node &&
node.type === 'MemberExpression' &&
node.object['name'] === '@_document' &&
node.property['name'] === 'head'
);
} }

@ -9,7 +9,7 @@ export default function mark_each_block_bindings(
) { ) {
// we need to ensure that the each block creates a context including // we need to ensure that the each block creates a context including
// the list and the index, if they're not otherwise referenced // the list and the index, if they're not otherwise referenced
binding.expression.references.forEach(name => { binding.expression.references.forEach((name) => {
const each_block = parent.node.scope.get_owner(name); const each_block = parent.node.scope.get_owner(name);
if (each_block) { if (each_block) {
(each_block as EachBlock).has_binding = true; (each_block as EachBlock).has_binding = true;

@ -83,13 +83,13 @@ export default class Renderer {
} }
push() { push() {
const current = this.current = { value: '' }; const current = (this.current = { value: '' });
const literal = this.literal = { const literal = (this.literal = {
type: 'TemplateLiteral', type: 'TemplateLiteral',
expressions: [], expressions: [],
quasis: [] quasis: []
}; });
this.stack.push({ current, literal }); this.stack.push({ current, literal });
} }
@ -116,7 +116,7 @@ export default class Renderer {
} }
render(nodes: INode[], options: RenderOptions) { render(nodes: INode[], options: RenderOptions) {
nodes.forEach(node => { nodes.forEach((node) => {
const handler = handlers[node.type]; const handler = handlers[node.type];
if (!handler) { if (!handler) {

@ -18,7 +18,9 @@ export default function(node: AwaitBlock, renderer: Renderer, options: RenderOpt
__value.then(null, @noop); __value.then(null, @noop);
return ${pending}; return ${pending};
} }
return (function(${node.then_node ? node.then_node : ''}) { ${get_const_tags(node.then.const_tags)}; return ${then}; }(__value)); return (function(${node.then_node ? node.then_node : ''}) { ${get_const_tags(
node.then.const_tags
)}; return ${then}; }(__value));
}(${node.expression.node}) }(${node.expression.node})
`); `);
} }

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

@ -12,13 +12,16 @@ export default function(node: EachBlock, renderer: Renderer, options: RenderOpti
renderer.render(node.children, options); renderer.render(node.children, options);
const result = renderer.pop(); const result = renderer.pop();
const consequent = x`@each(${node.expression.node}, (${args}) => { ${get_const_tags(node.const_tags)}; return ${result} })`; const consequent = x`@each(${node.expression.node}, (${args}) => { ${get_const_tags(
node.const_tags
)}; return ${result} })`;
if (node.else) { if (node.else) {
renderer.push(); renderer.push();
renderer.render(node.else.children, options); renderer.render(node.else.children, options);
let alternate: Node = renderer.pop(); let alternate: Node = renderer.pop();
if (node.else.const_tags.length > 0) alternate = x`(() => { ${get_const_tags(node.else.const_tags)}; return ${alternate} })()`; if (node.else.const_tags.length > 0)
alternate = x`(() => { ${get_const_tags(node.else.const_tags)}; return ${alternate} })()`;
renderer.add_expression(x`${node.expression.node}.length ? ${consequent} : ${alternate}`); renderer.add_expression(x`${node.expression.node}.length ? ${consequent} : ${alternate}`);
} else { } else {

@ -1,5 +1,9 @@
import { is_void } from '../../../../shared/utils/names'; import { is_void } from '../../../../shared/utils/names';
import { get_attribute_expression, get_attribute_value, get_class_attribute_value } from './shared/get_attribute_value'; import {
get_attribute_expression,
get_attribute_value,
get_class_attribute_value
} from './shared/get_attribute_value';
import { boolean_attributes } from '../../../../shared/boolean_attributes'; import { boolean_attributes } from '../../../../shared/boolean_attributes';
import { is_name_contenteditable, is_contenteditable } from '../../utils/contenteditable'; import { is_name_contenteditable, is_contenteditable } from '../../utils/contenteditable';
import Renderer, { RenderOptions } from '../Renderer'; import Renderer, { RenderOptions } from '../Renderer';
@ -14,7 +18,6 @@ import { regex_starts_with_newline } from '../../../utils/patterns';
import { Node, Expression as ESExpression } from 'estree'; import { Node, Expression as ESExpression } from 'estree';
export default function (node: Element, renderer: Renderer, options: RenderOptions) { export default function (node: Element, renderer: Renderer, options: RenderOptions) {
const children = remove_whitespace_children(node.children, node.next); const children = remove_whitespace_children(node.children, node.next);
// awkward special case // awkward special case
@ -29,7 +32,7 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio
renderer.add_string('<'); renderer.add_string('<');
add_tag_name(); add_tag_name();
const class_expression_list = node.classes.map(class_directive => { const class_expression_list = node.classes.map((class_directive) => {
const { expression, name } = class_directive; const { expression, name } = class_directive;
const snippet = expression ? expression.node : x`#ctx.${name}`; // TODO is this right? const snippet = expression ? expression.node : x`#ctx.${name}`; // TODO is this right?
return x`${snippet} ? "${name}" : ""`; return x`${snippet} ? "${name}" : ""`;
@ -41,26 +44,31 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio
class_expression_list.length > 0 && class_expression_list.length > 0 &&
class_expression_list.reduce((lhs, rhs) => x`${lhs} + ' ' + ${rhs}`); class_expression_list.reduce((lhs, rhs) => x`${lhs} + ' ' + ${rhs}`);
const style_expression_list = node.styles.map(style_directive => { const style_expression_list = node.styles.map((style_directive) => {
let { name, important, expression: { node: expression } } = style_directive; let {
name,
important,
expression: { node: expression }
} = style_directive;
if (important) { if (important) {
expression = x`${expression} + ' !important'`; expression = x`${expression} + ' !important'`;
} }
return p`"${name}": ${expression}`; return p`"${name}": ${expression}`;
}); });
const style_expression = const style_expression = style_expression_list.length > 0 && x`{ ${style_expression_list} }`;
style_expression_list.length > 0 &&
x`{ ${style_expression_list} }`;
if (node.attributes.some(attr => attr.is_spread)) { if (node.attributes.some((attr) => attr.is_spread)) {
// TODO dry this out // TODO dry this out
const args = []; const args = [];
node.attributes.forEach(attribute => { node.attributes.forEach((attribute) => {
if (attribute.is_spread) { if (attribute.is_spread) {
args.push(x`@escape_object(${attribute.expression.node})`); args.push(x`@escape_object(${attribute.expression.node})`);
} else { } else {
const attr_name = node.namespace === namespaces.foreign ? attribute.name : fix_attribute_casing(attribute.name); const attr_name =
node.namespace === namespaces.foreign
? attribute.name
: fix_attribute_casing(attribute.name);
const name = attribute.name.toLowerCase(); const name = attribute.name.toLowerCase();
if (name === 'value' && node.name.toLowerCase() === 'textarea') { if (name === 'value' && node.name.toLowerCase() === 'textarea') {
node_contents = get_attribute_value(attribute); node_contents = get_attribute_value(attribute);
@ -82,13 +90,18 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio
} }
}); });
renderer.add_expression(x`@spread([${args}], { classes: ${class_expression}, styles: ${style_expression} })`); renderer.add_expression(
x`@spread([${args}], { classes: ${class_expression}, styles: ${style_expression} })`
);
} else { } else {
let add_class_attribute = !!class_expression; let add_class_attribute = !!class_expression;
let add_style_attribute = !!style_expression; let add_style_attribute = !!style_expression;
node.attributes.forEach(attribute => { node.attributes.forEach((attribute) => {
const name = attribute.name.toLowerCase(); const name = attribute.name.toLowerCase();
const attr_name = node.namespace === namespaces.foreign ? attribute.name : fix_attribute_casing(attribute.name); const attr_name =
node.namespace === namespaces.foreign
? attribute.name
: fix_attribute_casing(attribute.name);
if (name === 'value' && node.name.toLowerCase() === 'textarea') { if (name === 'value' && node.name.toLowerCase() === 'textarea') {
node_contents = get_attribute_value(attribute); node_contents = get_attribute_value(attribute);
} else if (attribute.is_true) { } else if (attribute.is_true) {
@ -100,21 +113,31 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio
) { ) {
// a boolean attribute with one non-Text chunk // a boolean attribute with one non-Text chunk
renderer.add_string(' '); renderer.add_string(' ');
renderer.add_expression(x`${(attribute.chunks[0] as Expression).node} ? "${attr_name}" : ""`); renderer.add_expression(
x`${(attribute.chunks[0] as Expression).node} ? "${attr_name}" : ""`
);
} else if (name === 'class' && class_expression) { } else if (name === 'class' && class_expression) {
add_class_attribute = false; add_class_attribute = false;
renderer.add_string(` ${attr_name}="`); renderer.add_string(` ${attr_name}="`);
renderer.add_expression(x`[${get_class_attribute_value(attribute)}, ${class_expression}].join(' ').trim()`); renderer.add_expression(
x`[${get_class_attribute_value(attribute)}, ${class_expression}].join(' ').trim()`
);
renderer.add_string('"'); renderer.add_string('"');
} else if (name === 'style' && style_expression) { } else if (name === 'style' && style_expression) {
add_style_attribute = false; add_style_attribute = false;
renderer.add_expression(x`@add_styles(@merge_ssr_styles(${get_attribute_value(attribute)}, ${style_expression}))`); renderer.add_expression(
x`@add_styles(@merge_ssr_styles(${get_attribute_value(attribute)}, ${style_expression}))`
);
} else if (attribute.chunks.length === 1 && attribute.chunks[0].type !== 'Text') { } else if (attribute.chunks.length === 1 && attribute.chunks[0].type !== 'Text') {
const snippet = (attribute.chunks[0] as Expression).node; const snippet = (attribute.chunks[0] as Expression).node;
renderer.add_expression(x`@add_attribute("${attr_name}", ${snippet}, ${boolean_attributes.has(name) ? 1 : 0})`); renderer.add_expression(
x`@add_attribute("${attr_name}", ${snippet}, ${boolean_attributes.has(name) ? 1 : 0})`
);
} else { } else {
renderer.add_string(` ${attr_name}="`); renderer.add_string(` ${attr_name}="`);
renderer.add_expression((name === 'class' ? get_class_attribute_value : get_attribute_value)(attribute)); renderer.add_expression(
(name === 'class' ? get_class_attribute_value : get_attribute_value)(attribute)
);
renderer.add_string('"'); renderer.add_string('"');
} }
}); });
@ -139,7 +162,8 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio
const value = get_attribute_expression(value_attribute); const value = get_attribute_expression(value_attribute);
const type = node.get_static_attribute_value('type'); const type = node.get_static_attribute_value('type');
const bound = expression.node; const bound = expression.node;
const condition = type === 'checkbox' ? x`~${bound}.indexOf(${value})` : x`${value} === ${bound}`; const condition =
type === 'checkbox' ? x`~${bound}.indexOf(${value})` : x`${value} === ${bound}`;
renderer.add_expression(x`${condition} ? @add_attribute("checked", true, 1) : ""`); renderer.add_expression(x`${condition} ? @add_attribute("checked", true, 1) : ""`);
} }
} else if (contenteditable && is_name_contenteditable(name)) { } else if (contenteditable && is_name_contenteditable(name)) {
@ -154,7 +178,9 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio
// NOTE: do not add "value" attribute on <select /> // NOTE: do not add "value" attribute on <select />
} else { } else {
const snippet = expression.node; const snippet = expression.node;
renderer.add_expression(x`@add_attribute("${name}", ${snippet}, ${boolean_attributes.has(name) ? 1 : 0})`); renderer.add_expression(
x`@add_attribute("${name}", ${snippet}, ${boolean_attributes.has(name) ? 1 : 0})`
);
} }
}); });
@ -173,7 +199,9 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio
renderer.render(children, options); renderer.render(children, options);
const result = renderer.pop(); const result = renderer.pop();
renderer.add_expression(x`($$value => $$value === void 0 ? ${result} : $$value)(${node_contents})`); renderer.add_expression(
x`($$value => $$value === void 0 ? ${result} : $$value)(${node_contents})`
);
} else { } else {
if (node.name === 'textarea') { if (node.name === 'textarea') {
// Two or more leading newlines are required to restore the leading newline immediately after `<textarea>`. // Two or more leading newlines are required to restore the leading newline immediately after `<textarea>`.
@ -211,7 +239,8 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio
if (node.is_dynamic_element) { if (node.is_dynamic_element) {
let content: Node = renderer.pop(); let content: Node = renderer.pop();
if (options.dev && node.children.length > 0) content = x`(() => { @validate_void_dynamic_element(#tag); return ${content}; })()`; if (options.dev && node.children.length > 0)
content = x`(() => { @validate_void_dynamic_element(#tag); return ${content}; })()`;
renderer.add_expression(x`((#tag) => { renderer.add_expression(x`((#tag) => {
${options.dev && x`@validate_dynamic_element(#tag)`} ${options.dev && x`@validate_dynamic_element(#tag)`}
return #tag ? ${content} : ''; return #tag ? ${content} : '';
@ -238,4 +267,3 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio
} }
} }
} }

@ -10,12 +10,14 @@ export default function (node: IfBlock, renderer: Renderer, options: RenderOptio
renderer.push(); renderer.push();
renderer.render(node.children, options); renderer.render(node.children, options);
let consequent: Node = renderer.pop(); let consequent: Node = renderer.pop();
if (node.const_tags.length > 0) consequent = x`(() => { ${get_const_tags(node.const_tags)}; return ${consequent} })()`; if (node.const_tags.length > 0)
consequent = x`(() => { ${get_const_tags(node.const_tags)}; return ${consequent} })()`;
renderer.push(); renderer.push();
if (node.else) renderer.render(node.else.children, options); if (node.else) renderer.render(node.else.children, options);
let alternate: Node = renderer.pop(); let alternate: Node = renderer.pop();
if (node.else && node.else.const_tags.length > 0) alternate = x`(() => { ${get_const_tags(node.else.const_tags)}; return ${alternate} })()`; if (node.else && node.else.const_tags.length > 0)
alternate = x`(() => { ${get_const_tags(node.else.const_tags)}; return ${alternate} })()`;
renderer.add_expression(x`${condition} ? ${consequent} : ${alternate}`); renderer.add_expression(x`${condition} ? ${consequent} : ${alternate}`);
} }

@ -10,7 +10,7 @@ function get_prop_value(attribute) {
if (attribute.chunks.length === 0) return x`''`; if (attribute.chunks.length === 0) return x`''`;
return attribute.chunks return attribute.chunks
.map(chunk => { .map((chunk) => {
if (chunk.type === 'Text') return string_literal(chunk.data); if (chunk.type === 'Text') return string_literal(chunk.data);
return chunk.node; return chunk.node;
}) })
@ -21,7 +21,7 @@ export default function(node: InlineComponent, renderer: Renderer, options: Rend
const binding_props = []; const binding_props = [];
const binding_fns = []; const binding_fns = [];
node.bindings.forEach(binding => { node.bindings.forEach((binding) => {
renderer.has_bindings = true; renderer.has_bindings = true;
// TODO this probably won't work for contextual bindings // TODO this probably won't work for contextual bindings
@ -31,25 +31,23 @@ export default function(node: InlineComponent, renderer: Renderer, options: Rend
binding_fns.push(p`${binding.name}: $$value => { ${snippet} = $$value; $$settled = false }`); binding_fns.push(p`${binding.name}: $$value => { ${snippet} = $$value; $$settled = false }`);
}); });
const uses_spread = node.attributes.find(attr => attr.is_spread); const uses_spread = node.attributes.find((attr) => attr.is_spread);
let props; let props;
if (uses_spread) { if (uses_spread) {
props = x`@_Object.assign({}, ${ props = x`@_Object.assign({}, ${node.attributes
node.attributes .map((attribute) => {
.map(attribute => {
if (attribute.is_spread) { if (attribute.is_spread) {
return attribute.expression.node; return attribute.expression.node;
} else { } else {
return x`{ ${attribute.name}: ${get_prop_value(attribute)} }`; return x`{ ${attribute.name}: ${get_prop_value(attribute)} }`;
} }
}) })
.concat(binding_props.map(p => x`{ ${p} }`)) .concat(binding_props.map((p) => x`{ ${p} }`))})`;
})`;
} else { } else {
props = x`{ props = x`{
${node.attributes.map(attribute => p`${attribute.name}: ${get_prop_value(attribute)}`)}, ${node.attributes.map((attribute) => p`${attribute.name}: ${get_prop_value(attribute)}`)},
${binding_props} ${binding_props}
}`; }`;
} }
@ -58,13 +56,12 @@ export default function(node: InlineComponent, renderer: Renderer, options: Rend
${binding_fns} ${binding_fns}
}`; }`;
const expression = ( const expression =
node.name === 'svelte:self' node.name === 'svelte:self'
? renderer.name ? renderer.name
: node.name === 'svelte:component' : node.name === 'svelte:component'
? x`(${node.expression.node}) || @missing_component` ? x`(${node.expression.node}) || @missing_component`
: node.name.split('.').reduce(((lhs, rhs) => x`${lhs}.${rhs}`) as any) : node.name.split('.').reduce(((lhs, rhs) => x`${lhs}.${rhs}`) as any);
);
const slot_fns = []; const slot_fns = [];
@ -73,14 +70,15 @@ export default function(node: InlineComponent, renderer: Renderer, options: Rend
if (children.length) { if (children.length) {
const slot_scopes = new Map(); const slot_scopes = new Map();
renderer.render(children, Object.assign({}, options, { renderer.render(
children,
Object.assign({}, options, {
slot_scopes slot_scopes
})); })
);
slot_scopes.forEach(({ input, output, statements }, name) => { slot_scopes.forEach(({ input, output, statements }, name) => {
slot_fns.push( slot_fns.push(p`${name}: (${input}) => { ${statements}; return ${output}; }`);
p`${name}: (${input}) => { ${statements}; return ${output}; }`
);
}); });
} }
@ -103,7 +101,9 @@ export default function(node: InlineComponent, renderer: Renderer, options: Rend
renderer.add_string('">'); renderer.add_string('">');
} }
renderer.add_expression(x`@validate_component(${expression}, "${node.name}").$$render($$result, ${props}, ${bindings}, ${slots})`); renderer.add_expression(
x`@validate_component(${expression}, "${node.name}").$$render($$result, ${props}, ${bindings}, ${slots})`
);
if (node.css_custom_properties.length > 0) { if (node.css_custom_properties.length > 0) {
if (node.namespace === namespaces.svg) { if (node.namespace === namespaces.svg) {

@ -4,9 +4,13 @@ import { x } from 'code-red';
import get_slot_data from '../../utils/get_slot_data'; import get_slot_data from '../../utils/get_slot_data';
import { get_slot_scope } from './shared/get_slot_scope'; import { get_slot_scope } from './shared/get_slot_scope';
export default function(node: Slot, renderer: Renderer, options: RenderOptions & { export default function (
node: Slot,
renderer: Renderer,
options: RenderOptions & {
slot_scopes: Map<any, any>; slot_scopes: Map<any, any>;
}) { }
) {
const slot_data = get_slot_data(node.values); const slot_data = get_slot_data(node.values);
const slot = node.get_static_attribute_value('slot'); const slot = node.get_static_attribute_value('slot');
const nearest_inline_component = node.find_nearest(/InlineComponent/); const nearest_inline_component = node.find_nearest(/InlineComponent/);
@ -27,9 +31,9 @@ export default function(node: Slot, renderer: Renderer, options: RenderOptions &
if (slot && nearest_inline_component) { if (slot && nearest_inline_component) {
const lets = node.lets; const lets = node.lets;
const seen = new Set(lets.map(l => l.name.name)); const seen = new Set(lets.map((l) => l.name.name));
nearest_inline_component.lets.forEach(l => { nearest_inline_component.lets.forEach((l) => {
if (!seen.has(l.name.name)) lets.push(l); if (!seen.has(l.name.name)) lets.push(l);
}); });
options.slot_scopes.set(slot, { options.slot_scopes.set(slot, {

@ -5,18 +5,25 @@ import { get_slot_scope } from './shared/get_slot_scope';
import InlineComponent from '../../nodes/InlineComponent'; import InlineComponent from '../../nodes/InlineComponent';
import { get_const_tags } from './shared/get_const_tags'; import { get_const_tags } from './shared/get_const_tags';
export default function(node: SlotTemplate, renderer: Renderer, options: RenderOptions & { export default function (
node: SlotTemplate,
renderer: Renderer,
options: RenderOptions & {
slot_scopes: Map<any, any>; slot_scopes: Map<any, any>;
}) { }
) {
const parent_inline_component = node.parent as InlineComponent; const parent_inline_component = node.parent as InlineComponent;
const children = remove_whitespace_children(node instanceof SlotTemplate ? node.children : [node], node.next); const children = remove_whitespace_children(
node instanceof SlotTemplate ? node.children : [node],
node.next
);
renderer.push(); renderer.push();
renderer.render(children, options); renderer.render(children, options);
const lets = node.lets; const lets = node.lets;
const seen = new Set(lets.map(l => l.name.name)); const seen = new Set(lets.map((l) => l.name.name));
parent_inline_component.lets.forEach(l => { parent_inline_component.lets.forEach((l) => {
if (!seen.has(l.name.name)) lets.push(l); if (!seen.has(l.name.name)) lets.push(l);
}); });
@ -26,7 +33,9 @@ export default function(node: SlotTemplate, renderer: Renderer, options: RenderO
if (node.slot_template_name === 'default') { if (node.slot_template_name === 'default') {
throw new Error('Found elements without slot attribute when using slot="default"'); throw new Error('Found elements without slot attribute when using slot="default"');
} }
throw new Error(`Duplicate slot name "${node.slot_template_name}" in <${parent_inline_component.name}>`); throw new Error(
`Duplicate slot name "${node.slot_template_name}" in <${parent_inline_component.name}>`
);
} }
options.slot_scopes.set(node.slot_template_name, { options.slot_scopes.set(node.slot_template_name, {

@ -1,4 +1,3 @@
import Renderer, { RenderOptions } from '../Renderer'; import Renderer, { RenderOptions } from '../Renderer';
import { x } from 'code-red'; import { x } from 'code-red';
@ -6,9 +5,7 @@ export default function(node, renderer: Renderer, _options: RenderOptions) {
const snippet = node.expression.node; const snippet = node.expression.node;
renderer.add_expression( renderer.add_expression(
node.parent && node.parent && node.parent.type === 'Element' && node.parent.name === 'style'
node.parent.type === 'Element' &&
node.parent.name === 'style'
? snippet ? snippet
: x`@escape(${snippet})` : x`@escape(${snippet})`
); );

@ -23,12 +23,13 @@ export function get_attribute_value(attribute: Attribute): ESTreeExpression {
* For value attribute of textarea, it will render as child node of `<textarea>` element. * For value attribute of textarea, it will render as child node of `<textarea>` element.
* Therefore, we need to escape as content (not attribute). * Therefore, we need to escape as content (not attribute).
*/ */
const is_textarea_value = attribute.parent.name.toLowerCase() === 'textarea' && attribute.name.toLowerCase() === 'value'; const is_textarea_value =
attribute.parent.name.toLowerCase() === 'textarea' && attribute.name.toLowerCase() === 'value';
return attribute.chunks return attribute.chunks
.map((chunk) => { .map((chunk) => {
return chunk.type === 'Text' return chunk.type === 'Text'
? string_literal(chunk.data.replace(regex_double_quotes, '&quot;')) as ESTreeExpression ? (string_literal(chunk.data.replace(regex_double_quotes, '&quot;')) as ESTreeExpression)
: x`@escape(${chunk.node}, ${is_textarea_value ? 'false' : 'true'})`; : x`@escape(${chunk.node}, ${is_textarea_value ? 'false' : 'true'})`;
}) })
.reduce((lhs, rhs) => x`${lhs} + ${rhs}`); .reduce((lhs, rhs) => x`${lhs} + ${rhs}`);

@ -5,7 +5,7 @@ export function get_const_tags(const_tags: ConstTag[]) {
return { return {
type: 'VariableDeclaration', type: 'VariableDeclaration',
kind: 'let', kind: 'let',
declarations: const_tags.map(const_tag => { declarations: const_tags.map((const_tag) => {
const assignment = const_tag.node.expression; const assignment = const_tag.node.expression;
return { return {
type: 'VariableDeclarator', type: 'VariableDeclarator',

@ -6,7 +6,7 @@ export function get_slot_scope(lets: Let[]): ObjectPattern {
return { return {
type: 'ObjectPattern', type: 'ObjectPattern',
properties: lets.map(l => { properties: lets.map((l) => {
return { return {
type: 'Property', type: 'Property',
kind: 'init', kind: 'init',

@ -41,10 +41,10 @@ export default function remove_whitespace_children(children: INode[], next?: INo
child.data = data; child.data = data;
nodes.unshift(child); nodes.unshift(child);
link(last_child, last_child = child); link(last_child, (last_child = child));
} else { } else {
nodes.unshift(child); nodes.unshift(child);
link(last_child, last_child = child); link(last_child, (last_child = child));
} }
} }
@ -69,7 +69,7 @@ function trimmable_at(child: INode, next_sibling: INode): boolean {
// The child and its sibling share a common nearest each block (not at an each block boundary) // The child and its sibling share a common nearest each block (not at an each block boundary)
// The next sibling's previous node is an each block // The next sibling's previous node is an each block
return ( return (
next_sibling.find_nearest(/EachBlock/) === next_sibling.find_nearest(/EachBlock/) === child.find_nearest(/EachBlock/) ||
child.find_nearest(/EachBlock/) || next_sibling.prev.type === 'EachBlock' next_sibling.prev.type === 'EachBlock'
); );
} }

@ -23,28 +23,40 @@ export default function ssr(
const { name } = component; const { name } = component;
// create $$render function // create $$render function
renderer.render(trim(component.fragment.children), Object.assign({ renderer.render(
trim(component.fragment.children),
Object.assign(
{
locate: component.locate locate: component.locate
}, options)); },
options
)
);
// TODO put this inside the Renderer class // TODO put this inside the Renderer class
const literal = renderer.pop(); const literal = renderer.pop();
// TODO concatenate CSS maps // TODO concatenate CSS maps
const css = options.customElement ? const css = options.customElement
{ code: null, map: null } : ? { code: null, map: null }
component.stylesheet.render(options.filename); : component.stylesheet.render(options.filename);
const uses_rest = component.var_lookup.has('$$restProps'); const uses_rest = component.var_lookup.has('$$restProps');
const props = component.vars.filter(variable => !variable.module && variable.export_name); const props = component.vars.filter((variable) => !variable.module && variable.export_name);
const rest = uses_rest ? b`let $$restProps = @compute_rest_props($$props, [${props.map(prop => `"${prop.export_name}"`).join(',')}]);` : null; const rest = uses_rest
? b`let $$restProps = @compute_rest_props($$props, [${props
.map((prop) => `"${prop.export_name}"`)
.join(',')}]);`
: null;
const uses_slots = component.var_lookup.has('$$slots'); const uses_slots = component.var_lookup.has('$$slots');
const slots = uses_slots ? b`let $$slots = @compute_slots(#slots);` : null; const slots = uses_slots ? b`let $$slots = @compute_slots(#slots);` : null;
const reactive_stores = component.vars.filter(variable => variable.name[0] === '$' && variable.name[1] !== '$'); const reactive_stores = component.vars.filter(
(variable) => variable.name[0] === '$' && variable.name[1] !== '$'
);
const reactive_store_subscriptions = reactive_stores const reactive_store_subscriptions = reactive_stores
.filter(store => { .filter((store) => {
const variable = component.var_lookup.get(store.name.slice(1)); const variable = component.var_lookup.get(store.name.slice(1));
return !variable || variable.hoistable; return !variable || variable.hoistable;
}) })
@ -59,8 +71,7 @@ export default function ssr(
({ name }) => b`${`$$unsubscribe_${name.slice(1)}`}()` ({ name }) => b`${`$$unsubscribe_${name.slice(1)}`}()`
); );
const reactive_store_declarations = reactive_stores const reactive_store_declarations = reactive_stores.map(({ name }) => {
.map(({ name }) => {
const store_name = name.slice(1); const store_name = name.slice(1);
const store = component.var_lookup.get(store_name); const store = component.var_lookup.get(store_name);
@ -96,27 +107,19 @@ export default function ssr(
for (const name of names) { for (const name of names) {
const variable = component.var_lookup.get(name); const variable = component.var_lookup.get(name);
if (variable && if (
variable &&
!variable.hoistable && !variable.hoistable &&
!variable.global && !variable.global &&
!variable.module && !variable.module &&
( (variable.subscribable || variable.name[0] === '$')
variable.subscribable || variable.name[0] === '$' ) {
)) {
to_invalidate.add(variable.name); to_invalidate.add(variable.name);
} }
} }
if (to_invalidate.size) { if (to_invalidate.size) {
this.replace( this.replace(invalidate({ component } as any, scope, node, to_invalidate, true));
invalidate(
{ component } as any,
scope,
node,
to_invalidate,
true
)
);
} }
} }
} }
@ -142,23 +145,24 @@ export default function ssr(
// TODO only do this for props with a default value // TODO only do this for props with a default value
const parent_bindings = instance_javascript const parent_bindings = instance_javascript
? component.vars ? component.vars
.filter(variable => !variable.module && variable.export_name) .filter((variable) => !variable.module && variable.export_name)
.map(prop => { .map((prop) => {
return b`if ($$props.${prop.export_name} === void 0 && $$bindings.${prop.export_name} && ${prop.name} !== void 0) $$bindings.${prop.export_name}(${prop.name});`; return b`if ($$props.${prop.export_name} === void 0 && $$bindings.${prop.export_name} && ${prop.name} !== void 0) $$bindings.${prop.export_name}(${prop.name});`;
}) })
: []; : [];
const injected = Array.from(component.injected_reactive_declaration_vars).filter(name => { const injected = Array.from(component.injected_reactive_declaration_vars).filter((name) => {
const variable = component.var_lookup.get(name); const variable = component.var_lookup.get(name);
return variable.injected; return variable.injected;
}); });
const reactive_declarations = component.reactive_declarations.map(d => { const reactive_declarations = component.reactive_declarations.map((d) => {
const body: Statement = (d.node as LabeledStatement).body; const body: Statement = (d.node as LabeledStatement).body;
let statement = b`${body}`; let statement = b`${body}`;
if (!d.declaration) { // TODO do not add label if it's not referenced if (!d.declaration) {
// TODO do not add label if it's not referenced
statement = b`$: { ${statement} }`; statement = b`$: { ${statement} }`;
} }
@ -190,7 +194,7 @@ export default function ssr(
return ${literal};`; return ${literal};`;
const blocks = [ const blocks = [
...injected.map(name => b`let ${name};`), ...injected.map((name) => b`let ${name};`),
rest, rest,
slots, slots,
...reactive_store_declarations, ...reactive_store_declarations,
@ -204,11 +208,15 @@ export default function ssr(
const css_sourcemap_enabled = check_enable_sourcemap(options.enableSourcemap, 'css'); const css_sourcemap_enabled = check_enable_sourcemap(options.enableSourcemap, 'css');
const js = b` const js = b`
${css.code ? b` ${
css.code
? b`
const #css = { const #css = {
code: "${css.code}", code: "${css.code}",
map: ${css_sourcemap_enabled && css.map ? string_literal(css.map.toString()) : 'null'} map: ${css_sourcemap_enabled && css.map ? string_literal(css.map.toString()) : 'null'}
};` : null} };`
: null
}
${component.extract_javascript(component.ast.module)} ${component.extract_javascript(component.ast.module)}

@ -85,5 +85,4 @@ describe('contenteditable', () => {
assert.equal(get_contenteditable_attr(node), undefined); assert.equal(get_contenteditable_attr(node), undefined);
}); });
}); });
}); });

@ -9,7 +9,7 @@ import Attribute from '../nodes/Attribute';
import { regex_whitespaces } from '../../utils/patterns'; import { regex_whitespaces } from '../../utils/patterns';
const aria_roles = roles_map.keys(); const aria_roles = roles_map.keys();
const abstract_roles = new Set(aria_roles.filter(role => roles_map.get(role).abstract)); const abstract_roles = new Set(aria_roles.filter((role) => roles_map.get(role).abstract));
const non_abstract_roles = aria_roles.filter((name) => !abstract_roles.has(name)); const non_abstract_roles = aria_roles.filter((name) => !abstract_roles.has(name));
const non_interactive_roles = new Set( const non_interactive_roles = new Set(
@ -33,7 +33,8 @@ const non_interactive_roles = new Set(
); );
const interactive_roles = new Set( const interactive_roles = new Set(
non_abstract_roles.filter((name) => non_abstract_roles.filter(
(name) =>
!non_interactive_roles.has(name) && !non_interactive_roles.has(name) &&
// 'generic' is meant to have no semantic meaning. // 'generic' is meant to have no semantic meaning.
name !== 'generic' name !== 'generic'
@ -58,7 +59,10 @@ export function is_presentation_role(role: ARIARoleDefinitionKey) {
return presentation_roles.has(role); return presentation_roles.has(role);
} }
export function is_hidden_from_screen_reader(tag_name: string, attribute_map: Map<string, Attribute>) { export function is_hidden_from_screen_reader(
tag_name: string,
attribute_map: Map<string, Attribute>
) {
if (tag_name === 'input') { if (tag_name === 'input') {
const type = attribute_map.get('type')?.get_static_value(); const type = attribute_map.get('type')?.get_static_value();
@ -113,7 +117,9 @@ const interactive_ax_objects = new Set(
); );
const non_interactive_ax_objects = new Set( const non_interactive_ax_objects = new Set(
[...AXObjects.keys()].filter((name) => ['windows', 'structure'].includes(AXObjects.get(name).type)) [...AXObjects.keys()].filter((name) =>
['windows', 'structure'].includes(AXObjects.get(name).type)
)
); );
const interactive_element_ax_object_schemas: ARIARoleRelationConcept[] = []; const interactive_element_ax_object_schemas: ARIARoleRelationConcept[] = [];
@ -142,10 +148,7 @@ function match_schema(
return schema.attributes.every((schema_attribute) => { return schema.attributes.every((schema_attribute) => {
const attribute = attribute_map.get(schema_attribute.name); const attribute = attribute_map.get(schema_attribute.name);
if (!attribute) return false; if (!attribute) return false;
if ( if (schema_attribute.value && schema_attribute.value !== attribute.get_static_value()) {
schema_attribute.value &&
schema_attribute.value !== attribute.get_static_value()
) {
return false; return false;
} }
return true; return true;
@ -155,7 +158,7 @@ function match_schema(
export enum ElementInteractivity { export enum ElementInteractivity {
Interactive = 'interactive', Interactive = 'interactive',
NonInteractive = 'non-interactive', NonInteractive = 'non-interactive',
Static = 'static', Static = 'static'
} }
export function element_interactivity( export function element_interactivity(
@ -163,9 +166,7 @@ export function element_interactivity(
attribute_map: Map<string, Attribute> attribute_map: Map<string, Attribute>
): ElementInteractivity { ): ElementInteractivity {
if ( if (
interactive_element_role_schemas.some((schema) => interactive_element_role_schemas.some((schema) => match_schema(schema, tag_name, attribute_map))
match_schema(schema, tag_name, attribute_map)
)
) { ) {
return ElementInteractivity.Interactive; return ElementInteractivity.Interactive;
} }
@ -198,23 +199,42 @@ export function element_interactivity(
return ElementInteractivity.Static; return ElementInteractivity.Static;
} }
export function is_interactive_element(tag_name: string, attribute_map: Map<string, Attribute>): boolean { export function is_interactive_element(
tag_name: string,
attribute_map: Map<string, Attribute>
): boolean {
return element_interactivity(tag_name, attribute_map) === ElementInteractivity.Interactive; return element_interactivity(tag_name, attribute_map) === ElementInteractivity.Interactive;
} }
export function is_non_interactive_element(tag_name: string, attribute_map: Map<string, Attribute>): boolean { export function is_non_interactive_element(
tag_name: string,
attribute_map: Map<string, Attribute>
): boolean {
return element_interactivity(tag_name, attribute_map) === ElementInteractivity.NonInteractive; return element_interactivity(tag_name, attribute_map) === ElementInteractivity.NonInteractive;
} }
export function is_static_element(tag_name: string, attribute_map: Map<string, Attribute>): boolean { export function is_static_element(
tag_name: string,
attribute_map: Map<string, Attribute>
): boolean {
return element_interactivity(tag_name, attribute_map) === ElementInteractivity.Static; return element_interactivity(tag_name, attribute_map) === ElementInteractivity.Static;
} }
export function is_semantic_role_element(role: ARIARoleDefinitionKey, tag_name: string, attribute_map: Map<string, Attribute>) { export function is_semantic_role_element(
role: ARIARoleDefinitionKey,
tag_name: string,
attribute_map: Map<string, Attribute>
) {
for (const [schema, ax_object] of elementAXObjects.entries()) { for (const [schema, ax_object] of elementAXObjects.entries()) {
if (schema.name === tag_name && (!schema.attributes || schema.attributes.every( if (
(attr) => attribute_map.has(attr.name) && attribute_map.get(attr.name).get_static_value() === attr.value schema.name === tag_name &&
))) { (!schema.attributes ||
schema.attributes.every(
(attr) =>
attribute_map.has(attr.name) &&
attribute_map.get(attr.name).get_static_value() === attr.value
))
) {
for (const name of ax_object) { for (const name of ax_object) {
const roles = AXObjectRoles.get(name); const roles = AXObjectRoles.get(name);
if (roles) { if (roles) {

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save