Merge pull request #2838 from zxbodya/improve-typings

Improve typings
pull/2876/head
Rich Harris 5 years ago committed by GitHub
commit ca8c856ed6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

1
.gitignore vendored

@ -1,3 +1,4 @@
.idea
.DS_Store
.nyc_output
node_modules

8
package-lock.json generated

@ -1,6 +1,6 @@
{
"name": "svelte",
"version": "3.4.1",
"version": "3.4.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -4179,9 +4179,9 @@
"dev": true
},
"typescript": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.4.tgz",
"integrity": "sha512-0RNDbSdEokBeEAkgNbxJ+BLwSManFy9TeXz8uW+48j/xhEXv1ePME60olyzw2XzUqUBNAYFeJadIqAgNqIACwg==",
"version": "3.4.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.5.tgz",
"integrity": "sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw==",
"dev": true
},
"uglify-js": {

@ -32,7 +32,8 @@
"pretest": "npm run build",
"posttest": "agadoo src/internal/index.js",
"prepublishOnly": "export PUBLISH=true && npm run lint && npm test",
"tsd": "tsc -d src/store.ts --outDir ."
"tsd": "tsc -d src/store.ts --outDir .",
"typecheck": "tsc --noEmit"
},
"repository": {
"type": "git",
@ -84,7 +85,7 @@
"tiny-glob": "^0.2.1",
"ts-node": "^8.0.2",
"tslib": "^1.8.0",
"typescript": "^3.0.1"
"typescript": "^3.4.0"
},
"nyc": {
"include": [

@ -1,4 +1,5 @@
import MagicString, { Bundle } from 'magic-string';
// @ts-ignore
import { walk, childKeys } from 'estree-walker';
import { getLocator } from 'locate-character';
import Stats from '../Stats';
@ -21,6 +22,7 @@ import { remove_indentation, add_indentation } from '../utils/indentation';
import get_object from './utils/get_object';
import unwrap_parens from './utils/unwrap_parens';
import Slot from './nodes/Slot';
import { Node as ESTreeNode } from 'estree';
type ComponentOptions = {
namespace?: string;
@ -758,7 +760,7 @@ export default class Component {
});
}
if (is_reference(node, parent)) {
if (is_reference(node as ESTreeNode, parent as ESTreeNode)) {
const object = get_object(node);
const { name } = object;
@ -776,7 +778,7 @@ export default class Component {
});
}
invalidate(name, value) {
invalidate(name, value?) {
const variable = this.var_lookup.get(name);
if (variable && (variable.subscribable && variable.reassigned)) {
@ -1022,7 +1024,7 @@ export default class Component {
scope = map.get(node);
}
if (is_reference(node, parent)) {
if (is_reference(node as ESTreeNode, parent as ESTreeNode)) {
const { name } = flatten_reference(node);
const owner = scope.find_owner(name);
@ -1113,7 +1115,7 @@ export default class Component {
} else if (node.type === 'UpdateExpression') {
const identifier = get_object(node.argument);
assignees.add(identifier.name);
} else if (is_reference(node, parent)) {
} else if (is_reference(node as ESTreeNode, parent as ESTreeNode)) {
const identifier = get_object(node);
if (!assignee_nodes.has(identifier)) {
const { name } = identifier;

@ -55,7 +55,7 @@ function validate_options(options: CompileOptions, warnings: Warning[]) {
}
}
function get_name(filename) {
function get_name(filename: string) {
if (!filename) return null;
const parts = filename.split(/[\/\\]/);
@ -105,4 +105,4 @@ export default function compile(source: string, options: CompileOptions = {}) {
: render_dom(component, options);
return component.generate(js);
}
}

@ -67,6 +67,7 @@ export default class Attribute extends Node {
this.should_cache = this.is_dynamic
? this.chunks.length === 1
// @ts-ignore todo: probably error
? this.chunks[0].node.type !== 'Identifier' || scope.names.has(this.chunks[0].node.name)
: true
: false;
@ -91,8 +92,10 @@ export default class Attribute extends Node {
if (this.chunks.length === 0) return `""`;
if (this.chunks.length === 1) {
return this.chunks[0].type === 'Text'
? stringify(this.chunks[0].data)
? stringify((this.chunks[0] as Text).data)
// @ts-ignore todo: probably error
: this.chunks[0].render(block);
}
@ -102,6 +105,7 @@ export default class Attribute extends Node {
if (chunk.type === 'Text') {
return stringify(chunk.data);
} else {
// @ts-ignore todo: probably error
return chunk.get_precedence() <= 13 ? `(${chunk.render()})` : chunk.render();
}
})
@ -114,7 +118,8 @@ export default class Attribute extends Node {
return this.is_true
? true
: this.chunks[0]
? this.chunks[0].data
// method should be called only when `is_static = true`
? (this.chunks[0] as Text).data
: '';
}
}

@ -5,6 +5,7 @@ import CatchBlock from './CatchBlock';
import Expression from './shared/Expression';
export default class AwaitBlock extends Node {
type: 'AwaitBlock';
expression: Expression;
value: string;
error: string;

@ -5,6 +5,7 @@ import Component from '../Component';
import TemplateScope from './shared/TemplateScope';
export default class Binding extends Node {
type: 'Binding';
name: string;
expression: Expression;
is_contextual: boolean;

@ -3,6 +3,7 @@ import TemplateScope from './shared/TemplateScope';
import AbstractBlock from './shared/AbstractBlock';
export default class CatchBlock extends AbstractBlock {
type: 'CatchBlock';
scope: TemplateScope;
constructor(component, parent, scope, info) {

@ -2,6 +2,7 @@ import Node from './shared/Node';
import Expression from './shared/Expression';
export default class DebugTag extends Node {
type: 'DebugTag';
expressions: Expression[];
constructor(component, parent, scope, info) {
@ -11,4 +12,4 @@ export default class DebugTag extends Node {
return new Expression(component, parent, scope, node);
});
}
}
}

@ -6,8 +6,15 @@ import TemplateScope from './shared/TemplateScope';
import AbstractBlock from './shared/AbstractBlock';
import { Node as INode } from '../../interfaces';
import { new_tail } from '../utils/tail';
import Element from './Element';
function unpack_destructuring(contexts: Array<{ name: string, tail: string }>, node: INode, tail: string) {
type Context = {
key: INode,
name?: string,
tail: string
};
function unpack_destructuring(contexts: Array<Context>, node: INode, tail: string) {
if (!node) return;
if (node.type === 'Identifier' || node.type === 'RestIdentifier') {
@ -53,7 +60,7 @@ export default class EachBlock extends AbstractBlock {
context: string;
key: Expression;
scope: TemplateScope;
contexts: Array<{ name: string, tail: string }>;
contexts: Array<Context>;
has_animation: boolean;
has_binding = false;
@ -82,7 +89,7 @@ export default class EachBlock extends AbstractBlock {
if (this.index) {
// index can only change if this is a keyed each block
const dependencies = this.key ? this.expression.dependencies : [];
const dependencies = this.key ? this.expression.dependencies : new Set([]);
this.scope.add(this.index, dependencies, this);
}
@ -92,8 +99,8 @@ export default class EachBlock extends AbstractBlock {
if (this.has_animation) {
if (this.children.length !== 1) {
const child = this.children.find(child => !!child.animation);
component.error(child.animation, {
const child = this.children.find(child => !!(child as Element).animation);
component.error((child as Element).animation, {
code: `invalid-animation`,
message: `An element that use the animate directive must be the sole child of a keyed each block`
});

@ -15,6 +15,7 @@ import fuzzymatch from '../../utils/fuzzymatch';
import list from '../../utils/list';
import Let from './Let';
import TemplateScope from './shared/TemplateScope';
import { INode } from './interfaces';
const svg = /^(?:altGlyph|altGlyphDef|altGlyphItem|animate|animateColor|animateMotion|animateTransform|circle|clipPath|color-profile|cursor|defs|desc|discard|ellipse|feBlend|feColorMatrix|feComponentTransfer|feComposite|feConvolveMatrix|feDiffuseLighting|feDisplacementMap|feDistantLight|feDropShadow|feFlood|feFuncA|feFuncB|feFuncG|feFuncR|feGaussianBlur|feImage|feMerge|feMergeNode|feMorphology|feOffset|fePointLight|feSpecularLighting|feSpotLight|feTile|feTurbulence|filter|font|font-face|font-face-format|font-face-name|font-face-src|font-face-uri|foreignObject|g|glyph|glyphRef|hatch|hatchpath|hkern|image|line|linearGradient|marker|mask|mesh|meshgradient|meshpatch|meshrow|metadata|missing-glyph|mpath|path|pattern|polygon|polyline|radialGradient|rect|set|solidcolor|stop|svg|switch|symbol|text|textPath|tref|tspan|unknown|use|view|vkern)$/;
@ -101,7 +102,7 @@ export default class Element extends Node {
intro?: Transition = null;
outro?: Transition = null;
animation?: Animation = null;
children: Node[];
children: INode[];
namespace: string;
constructor(component, parent, scope, info: any) {
@ -136,7 +137,7 @@ export default class Element extends Node {
// Special case — treat these the same way:
// <option>{foo}</option>
// <option value={foo}>{foo}</option>
const value_attribute = info.attributes.find((attribute: Node) => attribute.name === 'value');
const value_attribute = info.attributes.find(attribute => attribute.name === 'value');
if (!value_attribute) {
info.attributes.push({
@ -228,7 +229,7 @@ export default class Element extends Node {
let is_figure_parent = false;
while (parent) {
if (parent.name === 'figure') {
if ((parent as Element).name === 'figure') {
is_figure_parent = true;
break;
}
@ -253,7 +254,7 @@ export default class Element extends Node {
return true;
});
const index = children.findIndex(child => child.name === 'figcaption');
const index = children.findIndex(child => (child as Element).name === 'figcaption');
if (index !== -1 && (index !== 0 && index !== children.length - 1)) {
this.component.warn(children[index], {
@ -320,7 +321,9 @@ export default class Element extends Node {
}
const value = attribute.get_static_value();
// @ts-ignore
if (value && !aria_role_set.has(value)) {
// @ts-ignore
const match = fuzzymatch(value, aria_roles);
let message = `A11y: Unknown role '${value}'`;
if (match) message += ` (did you mean '${match}'?)`;
@ -359,6 +362,7 @@ export default class Element extends Node {
// tabindex-no-positive
if (name === 'tabindex') {
const value = attribute.get_static_value();
// @ts-ignore todo is tabindex=true correct case?
if (!isNaN(value) && +value > 0) {
component.warn(attribute, {
code: `a11y-positive-tabindex`,

@ -1,10 +1,11 @@
import map_children from './shared/map_children';
import AbstractBlock from './shared/AbstractBlock';
import Component from '../Component';
export default class ElseBlock extends AbstractBlock {
type: 'ElseBlock';
constructor(component, parent, scope, info) {
constructor(component: Component, parent, scope, info) {
super(component, parent, scope, info);
this.children = map_children(component, this, scope, info.children);

@ -5,6 +5,7 @@ import deindent from '../utils/deindent';
import Block from '../render-dom/Block';
export default class EventHandler extends Node {
type: 'EventHandler';
name: string;
modifiers: Set<string>;
expression: Expression;
@ -65,4 +66,4 @@ export default class EventHandler extends Node {
// this.component.add_reference(this.handler_name);
return `ctx.${this.handler_name}`;
}
}
}

@ -3,10 +3,12 @@ import Component from '../Component';
import map_children from './shared/map_children';
import Block from '../render-dom/Block';
import TemplateScope from './shared/TemplateScope';
import { INode } from './interfaces';
export default class Fragment extends Node {
type: 'Fragment';
block: Block;
children: Node[];
children: INode[];
scope: TemplateScope;
constructor(component: Component, info: any) {
@ -16,4 +18,4 @@ export default class Fragment extends Node {
this.scope = scope;
this.children = map_children(component, this, scope, info.children);
}
}
}

@ -1,5 +1,4 @@
import Node from './shared/Node';
import Block from '../render-dom/Block';
import map_children from './shared/map_children';
export default class Head extends Node {

@ -7,6 +7,7 @@ import Expression from './shared/Expression';
import Component from '../Component';
import Let from './Let';
import TemplateScope from './shared/TemplateScope';
import { INode } from './interfaces';
export default class InlineComponent extends Node {
type: 'InlineComponent';
@ -16,7 +17,7 @@ export default class InlineComponent extends Node {
bindings: Binding[] = [];
handlers: EventHandler[] = [];
lets: Let[] = [];
children: Node[];
children: INode[];
scope: TemplateScope;
constructor(component: Component, parent, scope, info) {

@ -1,3 +1,5 @@
import Tag from './shared/Tag';
export default class MustacheTag extends Tag {}
export default class MustacheTag extends Tag {
type: 'MustacheTag';
}

@ -2,7 +2,7 @@ import map_children from './shared/map_children';
import AbstractBlock from './shared/AbstractBlock';
export default class PendingBlock extends AbstractBlock {
type: 'PendingBlock';
constructor(component, parent, scope, info) {
super(component, parent, scope, info);
this.children = map_children(component, parent, scope, info.children);

@ -1,3 +1,5 @@
import Tag from './shared/Tag';
export default class RawMustacheTag extends Tag {}
export default class RawMustacheTag extends Tag {
type: 'RawMustacheTag'
}

@ -1,17 +1,17 @@
import Node from './shared/Node';
import Element from './Element';
import Attribute from './Attribute';
import Component from '../Component';
import TemplateScope from './shared/TemplateScope';
import { INode } from './interfaces';
export default class Slot extends Element {
type: 'Element';
name: string;
children: Node[];
children: INode[];
slot_name: string;
values: Map<string, Attribute> = new Map();
constructor(component: Component, parent: Node, scope: TemplateScope, info: any) {
constructor(component: Component, parent: INode, scope: TemplateScope, info: any) {
super(component, parent, scope, info);
info.attributes.forEach(attr => {
@ -68,4 +68,4 @@ export default class Slot extends Element {
component.slots.set(this.slot_name, this);
}
}
}

@ -1,13 +1,14 @@
import Node from './shared/Node';
import Component from '../Component';
import TemplateScope from './shared/TemplateScope';
import { INode } from './interfaces';
export default class Text extends Node {
type: 'Text';
data: string;
use_space = false;
constructor(component: Component, parent: Node, scope: TemplateScope, info: any) {
constructor(component: Component, parent: INode, scope: TemplateScope, info: any) {
super(component, parent, scope, info);
this.data = info.data;
@ -23,4 +24,4 @@ export default class Text extends Node {
this.use_space = true;
}
}
}
}

@ -3,6 +3,7 @@ import TemplateScope from './shared/TemplateScope';
import AbstractBlock from './shared/AbstractBlock';
export default class ThenBlock extends AbstractBlock {
type: 'ThenBlock';
scope: TemplateScope;
constructor(component, parent, scope, info) {

@ -1,12 +1,13 @@
import Node from './shared/Node';
import map_children from './shared/map_children';
import map_children, { Children } from './shared/map_children';
import Component from '../Component';
export default class Title extends Node {
type: 'Title';
children: any[]; // TODO
children: Children;
should_cache: boolean;
constructor(component, parent, scope, info) {
constructor(component: Component, parent, scope, info) {
super(component, parent, scope, info);
this.children = map_children(component, parent, scope, info.children);
@ -33,4 +34,4 @@ export default class Title extends Node {
)
: true;
}
}
}

@ -0,0 +1,64 @@
import Tag from './shared/Tag';
import Action from './Action';
import Animation from './Animation';
import Attribute from './Attribute';
import AwaitBlock from './AwaitBlock';
import Binding from './Binding';
import Body from './Body';
import CatchBlock from './CatchBlock';
import Class from './Class';
import Comment from './Comment';
import DebugTag from './DebugTag';
import EachBlock from './EachBlock';
import Element from './Element';
import ElseBlock from './ElseBlock';
import EventHandler from './EventHandler';
import Fragment from './Fragment';
import Head from './Head';
import IfBlock from './IfBlock';
import InlineComponent from './InlineComponent';
import Let from './Let';
import MustacheTag from './MustacheTag';
import Options from './Options';
import PendingBlock from './PendingBlock';
import RawMustacheTag from './RawMustacheTag';
import Slot from './Slot';
import Text from './Text';
import ThenBlock from './ThenBlock';
import Title from './Title';
import Transition from './Transition';
import Window from './Window';
// note: to write less types each of types in union below should have type defined as literal
// https://www.typescriptlang.org/docs/handbook/advanced-types.html#discriminated-unions
export type INode = Action
| Animation
| Attribute
| AwaitBlock
| Binding
| Body
| CatchBlock
| Class
| Comment
| DebugTag
| EachBlock
| Element
| ElseBlock
| EventHandler
| Fragment
| Head
| IfBlock
| InlineComponent
| Let
| MustacheTag
| Options
| PendingBlock
| RawMustacheTag
| Slot
| Tag
| Text
| ThenBlock
| Title
| Transition
| Window;

@ -1,10 +1,11 @@
import Block from '../../render-dom/Block';
import Component from './../../Component';
import Node from './Node';
import { INode } from '../interfaces';
export default class AbstractBlock extends Node {
block: Block;
children: Node[];
children: INode[];
constructor(component: Component, parent, scope, info: any) {
super(component, parent, scope, info);

@ -12,6 +12,7 @@ import TemplateScope from './TemplateScope';
import get_object from '../../utils/get_object';
import { nodes_match } from '../../../utils/nodes_match';
import Block from '../../render-dom/Block';
import { INode } from '../interfaces';
const binary_operators: Record<string, number> = {
'**': 15,
@ -61,10 +62,12 @@ const precedence: Record<string, (node?: Node) => number> = {
SequenceExpression: () => 0
};
type Owner = Wrapper | INode;
export default class Expression {
type = 'Expression';
type: 'Expression' = 'Expression';
component: Component;
owner: Wrapper;
owner: Owner;
node: any;
snippet: string;
references: Set<string>;
@ -81,7 +84,8 @@ export default class Expression {
rendered: string;
constructor(component: Component, owner: Wrapper, template_scope: TemplateScope, info) {
// todo: owner type
constructor(component: Component, owner: Owner, template_scope: TemplateScope, info) {
// TODO revert to direct property access in prod?
Object.defineProperties(this, {
component: {
@ -92,6 +96,7 @@ export default class Expression {
this.node = info;
this.template_scope = template_scope;
this.owner = owner;
// @ts-ignore
this.is_synthetic = owner.is_synthetic;
const { dependencies, contextual_dependencies } = this;
@ -218,7 +223,7 @@ export default class Expression {
}
// TODO move this into a render-dom wrapper?
render(block: Block) {
render(block?: Block) {
if (this.rendered) return this.rendered;
const {
@ -510,4 +515,4 @@ function is_contextual(component: Component, scope: TemplateScope, name: string)
// assume contextual
return true;
}
}

@ -1,15 +1,17 @@
import Attribute from './../Attribute';
import Component from './../../Component';
import { INode } from '../interfaces';
import Text from '../Text';
export default class Node {
readonly start: number;
readonly end: number;
readonly component: Component;
readonly parent: Node;
readonly parent: INode;
readonly type: string;
prev?: Node;
next?: Node;
prev?: INode;
next?: INode;
can_use_innerhtml: boolean;
var: string;
@ -55,7 +57,7 @@ export default class Node {
if (attribute.chunks.length === 0) return '';
if (attribute.chunks.length === 1 && attribute.chunks[0].type === 'Text') {
return attribute.chunks[0].data;
return (attribute.chunks[0] as Text).data;
}
return null;

@ -2,6 +2,7 @@ import Node from './Node';
import Expression from './Expression';
export default class Tag extends Node {
type: 'MustacheTag' | 'RawMustacheTag';
expression: Expression;
should_cache: boolean;
@ -14,4 +15,4 @@ export default class Tag extends Node {
(this.expression.dependencies.size && scope.names.has(info.expression.name))
);
}
}
}

@ -2,6 +2,7 @@ import EachBlock from '../EachBlock';
import ThenBlock from '../ThenBlock';
import CatchBlock from '../CatchBlock';
import InlineComponent from '../InlineComponent';
import Element from '../Element';
type NodeWithScope = EachBlock | ThenBlock | CatchBlock | InlineComponent | Element;
@ -41,4 +42,4 @@ export default class TemplateScope {
const owner = this.get_owner(name);
return owner && (owner.type === 'Element' || owner.type === 'InlineComponent');
}
}
}

@ -14,9 +14,11 @@ import Slot from '../Slot';
import Text from '../Text';
import Title from '../Title';
import Window from '../Window';
import Node from './Node';
import { Node } from '../../../interfaces';
function get_constructor(type): typeof Node {
export type Children = ReturnType<typeof map_children>;
function get_constructor(type) {
switch (type) {
case 'AwaitBlock': return AwaitBlock;
case 'Body': return Body;
@ -38,7 +40,7 @@ function get_constructor(type): typeof Node {
}
}
export default function map_children(component, parent, scope, children: any[]) {
export default function map_children(component, parent, scope, children: Node[]) {
let last = null;
return children.map(child => {
const constructor = get_constructor(child.type);

@ -9,7 +9,7 @@ export interface BlockOptions {
renderer?: Renderer;
comment?: string;
key?: string;
bindings?: Map<string, () => { object: string, property: string, snippet: string }>;
bindings?: Map<string, { object: string, property: string, snippet: string }>;
dependencies?: Set<string>;
}

@ -3,7 +3,6 @@ import { CompileOptions } from '../../interfaces';
import Component from '../Component';
import FragmentWrapper from './wrappers/Fragment';
import CodeBuilder from '../utils/CodeBuilder';
import SlotWrapper from './wrappers/Slot';
export default class Renderer {
component: Component; // TODO Maybe Renderer shouldn't know about Component?
@ -18,6 +17,7 @@ export default class Renderer {
fragment: FragmentWrapper;
file_var: string;
locate: (c: number) => { line: number; column: number; };
constructor(component: Component, options: CompileOptions) {
this.component = component;
@ -58,4 +58,4 @@ export default class Renderer {
this.fragment.render(this.block, null, 'nodes');
}
}
}

@ -8,7 +8,7 @@ import deindent from '../../utils/deindent';
import ElseBlock from '../../nodes/ElseBlock';
import { attach_head } from '../../utils/tail';
class ElseBlockWrapper extends Wrapper {
export class ElseBlockWrapper extends Wrapper {
node: ElseBlock;
block: Block;
fragment: FragmentWrapper;
@ -83,6 +83,7 @@ export default class EachBlockWrapper extends Wrapper {
this.block = block.child({
comment: create_debugging_comment(this.node, this.renderer.component),
name: renderer.component.get_unique_name('create_each_block'),
// @ts-ignore todo: probably error
key: node.key as string,
bindings: new Map(block.bindings)
@ -310,7 +311,9 @@ export default class EachBlockWrapper extends Wrapper {
}
block.builders.init.add_block(deindent`
const ${get_key} = ctx => ${this.node.key.render()};
const ${get_key} = ctx => ${
// @ts-ignore todo: probably error
this.node.key.render()};
for (var #i = 0; #i < ${this.vars.each_block_value}.${length}; #i += 1) {
let child_ctx = ${this.vars.get_each_context}(ctx, ${this.vars.each_block_value}, #i);

@ -4,6 +4,7 @@ import fix_attribute_casing from './fix_attribute_casing';
import ElementWrapper from './index';
import { stringify } from '../../../utils/stringify';
import deindent from '../../../utils/deindent';
import Expression from '../../../nodes/shared/Expression';
export default class AttributeWrapper {
node: Attribute;
@ -21,7 +22,9 @@ export default class AttributeWrapper {
// special case — <option value={foo}> — see below
if (this.parent.node.name === 'option' && node.name === 'value') {
let select: ElementWrapper = this.parent;
while (select && (select.node.type !== 'Element' || select.node.name !== 'select')) select = select.parent;
while (select && (select.node.type !== 'Element' || select.node.name !== 'select'))
// @ts-ignore todo: doublecheck this, but looks to be correct
select = select.parent;
if (select && select.select_binding_dependencies) {
select.select_binding_dependencies.forEach(prop => {
@ -47,7 +50,7 @@ export default class AttributeWrapper {
(element.node.name === 'option' || // TODO check it's actually bound
(element.node.name === 'input' &&
element.node.bindings.find(
(binding: Binding) =>
(binding) =>
/checked|group/.test(binding.name)
)));
@ -78,13 +81,13 @@ export default class AttributeWrapper {
// DRY it out if that's possible without introducing crazy indirection
if (this.node.chunks.length === 1) {
// single {tag} — may be a non-string
value = this.node.chunks[0].render(block);
value = (this.node.chunks[0] as Expression).render(block);
} else {
// '{foo} {bar}' — treat as string concatenation
value =
(this.node.chunks[0].type === 'Text' ? '' : `"" + `) +
this.node.chunks
.map((chunk: Node) => {
.map((chunk) => {
if (chunk.type === 'Text') {
return stringify(chunk.data);
} else {

@ -1,14 +1,15 @@
import Attribute from '../../../nodes/Attribute';
import Block from '../../Block';
import AttributeWrapper from './Attribute';
import Node from '../../../nodes/shared/Node';
import ElementWrapper from '.';
import { stringify } from '../../../utils/stringify';
import add_to_set from '../../../utils/add_to_set';
import Expression from '../../../nodes/shared/Expression';
import Text from '../../../nodes/Text';
export interface StyleProp {
key: string;
value: Node[];
value: (Text|Expression)[];
}
export default class StyleAttributeWrapper extends AttributeWrapper {
@ -28,7 +29,7 @@ export default class StyleAttributeWrapper extends AttributeWrapper {
value =
((prop.value.length === 1 || prop.value[0].type === 'Text') ? '' : `"" + `) +
prop.value
.map((chunk: Node) => {
.map((chunk) => {
if (chunk.type === 'Text') {
return stringify(chunk.data);
} else {
@ -54,7 +55,7 @@ export default class StyleAttributeWrapper extends AttributeWrapper {
);
}
} else {
value = stringify(prop.value[0].data);
value = stringify((prop.value[0] as Text).data);
}
block.builders.hydrate.add_line(
@ -64,8 +65,8 @@ export default class StyleAttributeWrapper extends AttributeWrapper {
}
}
function optimize_style(value: Node[]) {
const props: { key: string, value: Node[] }[] = [];
function optimize_style(value: (Text|Expression)[]) {
const props: StyleProp[] = [];
let chunks = value.slice();
while (chunks.length) {
@ -87,7 +88,7 @@ function optimize_style(value: Node[]) {
end: chunk.end,
type: 'Text',
data: remaining_data
};
} as Text;
} else {
chunks.shift();
}
@ -101,8 +102,8 @@ function optimize_style(value: Node[]) {
return props;
}
function get_style_value(chunks: Node[]) {
const value: Node[] = [];
function get_style_value(chunks: (Text | Expression)[]) {
const value: (Text|Expression)[] = [];
let in_url = false;
let quote_mark = null;
@ -141,7 +142,7 @@ function get_style_value(chunks: Node[]) {
start: chunk.start,
end: chunk.start + c,
data: chunk.data.slice(0, c)
});
} as Text);
}
while (/[;\s]/.test(chunk.data[c])) c += 1;
@ -153,7 +154,7 @@ function get_style_value(chunks: Node[]) {
end: chunk.end,
type: 'Text',
data: remaining_data
});
} as Text);
break;
}
@ -170,6 +171,6 @@ function get_style_value(chunks: Node[]) {
};
}
function is_dynamic(value: Node[]) {
function is_dynamic(value: (Text|Expression)[]) {
return value.length > 1 || value[0].type !== 'Text';
}

@ -20,20 +20,19 @@ import add_event_handlers from '../shared/add_event_handlers';
import add_actions from '../shared/add_actions';
import create_debugging_comment from '../shared/create_debugging_comment';
import { get_context_merger } from '../shared/get_context_merger';
import Slot from '../../../nodes/Slot';
const events = [
{
event_names: ['input'],
filter: (node: Element, name: string) =>
node.name === 'textarea' ||
node.name === 'input' && !/radio|checkbox|range/.test(node.get_static_attribute_value('type'))
node.name === 'input' && !/radio|checkbox|range/.test(node.get_static_attribute_value('type') as string)
},
{
event_names: ['change'],
filter: (node: Element, name: string) =>
node.name === 'select' ||
node.name === 'input' && /radio|checkbox/.test(node.get_static_attribute_value('type'))
node.name === 'input' && /radio|checkbox/.test(node.get_static_attribute_value('type') as string)
},
{
event_names: ['change', 'input'],
@ -141,7 +140,7 @@ export default class ElementWrapper extends Wrapper {
}
if (owner && owner.node.type === 'InlineComponent') {
const name = attribute.get_static_value();
const name = attribute.get_static_value() as string;
if (!(owner as InlineComponentWrapper).slots.has(name)) {
const child_block = block.child({
@ -281,6 +280,7 @@ export default class ElementWrapper extends Wrapper {
if (!this.node.namespace && this.can_use_innerhtml && this.fragment.nodes.length > 0) {
if (this.fragment.nodes.length === 1 && this.fragment.nodes[0].node.type === 'Text') {
block.builders.create.add_line(
// @ts-ignore todo: should it be this.fragment.nodes[0].node.data instead?
`${node}.textContent = ${stringify(this.fragment.nodes[0].data)};`
);
} else {
@ -330,7 +330,7 @@ export default class ElementWrapper extends Wrapper {
function to_html(wrapper: ElementWrapper | TextWrapper) {
if (wrapper.node.type === 'Text') {
const { parent } = wrapper.node;
const parent = wrapper.node.parent as Element;
const raw = parent && (
parent.name === 'script' ||
@ -355,7 +355,7 @@ export default class ElementWrapper extends Wrapper {
if (is_void(wrapper.node.name)) return open + '>';
return `${open}>${wrapper.fragment.nodes.map(to_html).join('')}</${wrapper.node.name}>`;
return `${open}>${(wrapper as ElementWrapper).fragment.nodes.map(to_html).join('')}</${wrapper.node.name}>`;
}
if (renderer.options.dev) {
@ -382,8 +382,8 @@ export default class ElementWrapper extends Wrapper {
get_claim_statement(nodes: string) {
const attributes = this.node.attributes
.filter((attr: Node) => attr.type === 'Attribute')
.map((attr: Node) => `${quote_name_if_necessary(attr.name)}: true`)
.filter((attr) => attr.type === 'Attribute')
.map((attr) => `${quote_name_if_necessary(attr.name)}: true`)
.join(', ');
const name = this.node.namespace
@ -559,12 +559,13 @@ export default class ElementWrapper extends Wrapper {
}
add_attributes(block: Block) {
// @ts-ignore todo:
if (this.node.attributes.find(attr => attr.type === 'Spread')) {
this.add_spread_attributes(block);
return;
}
this.attributes.forEach((attribute: Attribute) => {
this.attributes.forEach((attribute) => {
if (attribute.node.name === 'class' && attribute.node.is_dynamic) {
this.class_dependencies.push(...attribute.node.dependencies);
}
@ -820,27 +821,28 @@ export default class ElementWrapper extends Wrapper {
});
}
add_css_class(class_name = this.component.stylesheet.id) {
const class_attribute = this.attributes.find(a => a.name === 'class');
if (class_attribute && !class_attribute.is_true) {
if (class_attribute.chunks.length === 1 && class_attribute.chunks[0].type === 'Text') {
(class_attribute.chunks[0] as Text).data += ` ${class_name}`;
} else {
(class_attribute.chunks as Node[]).push(
new Text(this.component, this, this.scope, {
type: 'Text',
data: ` ${class_name}`
})
);
}
} else {
this.attributes.push(
new Attribute(this.component, this, this.scope, {
type: 'Attribute',
name: 'class',
value: [{ type: 'Text', data: class_name }]
})
);
}
}
// todo: looks to be dead code copypasted from Element.add_css_class in src/compile/nodes/Element.ts
// add_css_class(class_name = this.component.stylesheet.id) {
// const class_attribute = this.attributes.find(a => a.name === 'class');
// if (class_attribute && !class_attribute.is_true) {
// if (class_attribute.chunks.length === 1 && class_attribute.chunks[0].type === 'Text') {
// (class_attribute.chunks[0] as Text).data += ` ${class_name}`;
// } else {
// (class_attribute.chunks as Node[]).push(
// new Text(this.component, this, this.scope, {
// type: 'Text',
// data: ` ${class_name}`
// })
// );
// }
// } else {
// this.attributes.push(
// new Attribute(this.component, this, this.scope, {
// type: 'Attribute',
// name: 'class',
// value: [{ type: 'Text', data: class_name }]
// })
// );
// }
// }
}

@ -13,7 +13,7 @@ import Slot from './Slot';
import Text from './Text';
import Title from './Title';
import Window from './Window';
import Node from '../../nodes/shared/Node';
import { INode } from '../../nodes/interfaces';
import TextWrapper from './Text';
import Renderer from '../Renderer';
import Block from '../Block';
@ -49,7 +49,7 @@ export default class FragmentWrapper {
constructor(
renderer: Renderer,
block: Block,
nodes: Node[],
nodes: INode[],
parent: Wrapper,
strip_whitespace: boolean,
next_sibling: Wrapper
@ -85,6 +85,7 @@ export default class FragmentWrapper {
// *unless* there is no whitespace between this node and its next sibling
if (this.nodes.length === 0) {
const should_trim = (
// @ts-ignore todo: probably error, should it be next_sibling.node.data?
next_sibling ? (next_sibling.node.type === 'Text' && /^\s/.test(next_sibling.data)) : !child.has_ancestor('EachBlock')
);
@ -96,6 +97,7 @@ export default class FragmentWrapper {
// glue text nodes (which could e.g. be separated by comments) together
if (last_child && last_child.node.type === 'Text') {
// @ts-ignore todo: probably error, should it be last_child.node.data?
last_child.data = data + last_child.data;
continue;
}

@ -96,7 +96,7 @@ export default class IfBlockWrapper extends Wrapper {
if (branch.block.has_outros) has_outros = true;
if (is_else_if(node.else)) {
create_branches(node.else.children[0]);
create_branches(node.else.children[0] as IfBlock);
} else if (node.else) {
const branch = new IfBlockBranch(
renderer,
@ -452,4 +452,4 @@ export default class IfBlockWrapper extends Wrapper {
block.builders.destroy.add_line(`${if_name}${name}.d(${parent_node ? '' : 'detaching'});`);
}
}
}

@ -142,7 +142,7 @@ export default class InlineComponentWrapper extends Wrapper {
if (this.fragment) {
const default_slot = this.slots.get('default');
this.fragment.nodes.forEach((child: Wrapper) => {
this.fragment.nodes.forEach((child) => {
child.render(default_slot.block, null, 'nodes');
});
}
@ -505,4 +505,4 @@ export default class InlineComponentWrapper extends Wrapper {
);
}
}
}
}

@ -1,13 +1,14 @@
import Renderer from '../Renderer';
import Block from '../Block';
import Node from '../../nodes/shared/Node';
import Tag from './shared/Tag';
import Wrapper from './shared/Wrapper';
import MustacheTag from '../../nodes/MustacheTag';
import RawMustacheTag from '../../nodes/RawMustacheTag';
export default class MustacheTagWrapper extends Tag {
var = 't';
constructor(renderer: Renderer, block: Block, parent: Wrapper, node: Node) {
constructor(renderer: Renderer, block: Block, parent: Wrapper, node: MustacheTag | RawMustacheTag) {
super(renderer, block, parent, node);
this.cannot_use_innerhtml();
}
@ -25,4 +26,4 @@ export default class MustacheTagWrapper extends Tag {
parent_node
);
}
}
}

@ -1,9 +1,10 @@
import Renderer from '../Renderer';
import Block from '../Block';
import Node from '../../nodes/shared/Node';
import Tag from './shared/Tag';
import Wrapper from './shared/wrapper';
import deindent from '../../utils/deindent';
import MustacheTag from '../../nodes/MustacheTag';
import RawMustacheTag from '../../nodes/RawMustacheTag';
export default class RawMustacheTagWrapper extends Tag {
var = 'raw';
@ -12,7 +13,7 @@ export default class RawMustacheTagWrapper extends Tag {
renderer: Renderer,
block: Block,
parent: Wrapper,
node: Node
node: MustacheTag | RawMustacheTag
) {
super(renderer, block, parent, node);
this.cannot_use_innerhtml();
@ -100,4 +101,4 @@ export default class RawMustacheTagWrapper extends Tag {
add_anchor_after();
}
}
}
}

@ -9,7 +9,6 @@ import add_to_set from '../../utils/add_to_set';
import get_slot_data from '../../utils/get_slot_data';
import { stringify_props } from '../../utils/stringify_props';
import Expression from '../../nodes/shared/Expression';
import Attribute from '../../nodes/Attribute';
export default class SlotWrapper extends Wrapper {
node: Slot;

@ -4,6 +4,7 @@ import Block from '../Block';
import Title from '../../nodes/Title';
import { stringify } from '../../utils/stringify';
import add_to_set from '../../utils/add_to_set';
import Text from '../../nodes/Text';
export default class TitleWrapper extends Wrapper {
node: Title;
@ -31,6 +32,7 @@ export default class TitleWrapper extends Wrapper {
// DRY it out if that's possible without introducing crazy indirection
if (this.node.children.length === 1) {
// single {tag} — may be a non-string
// @ts-ignore todo: check this
const { expression } = this.node.children[0];
value = expression.render(block);
add_to_set(all_dependencies, expression.dependencies);
@ -39,16 +41,18 @@ export default class TitleWrapper extends Wrapper {
value =
(this.node.children[0].type === 'Text' ? '' : `"" + `) +
this.node.children
.map((chunk: Node) => {
.map((chunk) => {
if (chunk.type === 'Text') {
return stringify(chunk.data);
} else {
// @ts-ignore todo: check this
const snippet = chunk.expression.render(block);
// @ts-ignore todo: check this
chunk.expression.dependencies.forEach(d => {
all_dependencies.add(d);
});
// @ts-ignore todo: check this
return chunk.expression.get_precedence() <= 13 ? `(${snippet})` : snippet;
}
})
@ -88,8 +92,8 @@ export default class TitleWrapper extends Wrapper {
);
}
} else {
const value = stringify(this.node.children[0].data);
const value = stringify((this.node.children[0] as Text).data);
block.builders.hydrate.add_line(`document.title = ${value};`);
}
}
}
}

@ -6,6 +6,7 @@ import deindent from '../../utils/deindent';
import add_event_handlers from './shared/add_event_handlers';
import Window from '../../nodes/Window';
import add_actions from './shared/add_actions';
import { INode } from '../../nodes/interfaces';
const associated_events = {
innerWidth: 'resize',
@ -33,7 +34,7 @@ const readonly = new Set([
export default class WindowWrapper extends Wrapper {
node: Window;
constructor(renderer: Renderer, block: Block, parent: Wrapper, node: Node) {
constructor(renderer: Renderer, block: Block, parent: Wrapper, node: INode) {
super(renderer, block, parent, node);
}

@ -1,11 +1,11 @@
import Renderer from '../../Renderer';
import Node from '../../../nodes/shared/Node';
import Block from '../../Block';
import { INode } from '../../../nodes/interfaces';
export default class Wrapper {
renderer: Renderer;
parent: Wrapper;
node: Node;
node: INode;
prev: Wrapper | null;
next: Wrapper | null;
@ -17,7 +17,7 @@ export default class Wrapper {
renderer: Renderer,
block: Block,
parent: Wrapper,
node: Node
node: INode
) {
this.node = node;
@ -75,4 +75,8 @@ export default class Wrapper {
this.node.type === 'MustacheTag'
);
}
}
render(block: Block, parent_node: string, parent_nodes: string){
throw Error('Wrapper class is not renderable');
}
}

@ -1,8 +1,8 @@
import Component from '../../../Component';
import { Node } from '../../../../interfaces';
import { INode } from '../../../nodes/interfaces';
export default function create_debugging_comment(
node: Node,
node: INode,
component: Component
) {
const { locate, source } = component;
@ -19,6 +19,7 @@ export default function create_debugging_comment(
d = node.children.length ? node.children[0].start : node.start;
while (source[d - 1] !== '>') d -= 1;
} else {
// @ts-ignore
d = node.expression ? node.expression.node.end : c;
while (source[d] !== '}') d += 1;
while (source[d] === '}') d += 1;

@ -12,6 +12,7 @@ import Tag from './handlers/Tag';
import Text from './handlers/Text';
import Title from './handlers/Title';
import { AppendTarget, CompileOptions } from '../../interfaces';
import { INode } from '../nodes/interfaces';
type Handler = (node: any, renderer: Renderer, options: CompileOptions) => void;
@ -36,6 +37,10 @@ const handlers: Record<string, Handler> = {
Window: noop
};
export interface RenderOptions extends CompileOptions{
locate: (c: number) => { line: number; column: number; };
};
export default class Renderer {
has_bindings = false;
code = '';
@ -51,7 +56,7 @@ export default class Renderer {
}
}
render(nodes, options) {
render(nodes: INode[], options: RenderOptions) {
nodes.forEach(node => {
const handler = handlers[node.type];

@ -1,8 +1,8 @@
import Renderer from '../Renderer';
import { CompileOptions } from '../../../interfaces';
import Renderer, { RenderOptions } from '../Renderer';
import { snip } from '../../utils/snip';
import AwaitBlock from '../../nodes/AwaitBlock';
export default function(node, renderer: Renderer, options: CompileOptions) {
export default function(node: AwaitBlock, renderer: Renderer, options: RenderOptions) {
renderer.append('${(function(__value) { if(@is_promise(__value)) return `');
renderer.render(node.pending.children, options);
@ -13,4 +13,4 @@ export default function(node, renderer: Renderer, options: CompileOptions) {
const snippet = snip(node.expression);
renderer.append(`\`;}(__value);}(${snippet})) }`);
}
}

@ -1,8 +1,8 @@
import Renderer from '../Renderer';
import { CompileOptions } from '../../../interfaces';
import Renderer, { RenderOptions } from '../Renderer';
import Comment from '../../nodes/Comment';
export default function(node, renderer: Renderer, options: CompileOptions) {
export default function(node: Comment, renderer: Renderer, options: RenderOptions) {
if (options.preserveComments) {
renderer.append(`<!--${node.data}-->`);
}
}
}

@ -1,9 +1,10 @@
import { stringify } from '../../utils/stringify';
export default function(node, renderer, options) {
import DebugTag from '../../nodes/DebugTag';
import Renderer, { RenderOptions } from '../Renderer';
export default function(node: DebugTag, renderer: Renderer, options: RenderOptions) {
if (!options.dev) return;
const filename = options.file || null;
const filename = options.filename || null;
const { line, column } = options.locate(node.start + 1);
const obj = node.expressions.length === 0
@ -15,4 +16,4 @@ export default function(node, renderer, options) {
const str = '${@debug(' + `${filename && stringify(filename)}, ${line}, ${column}, ${obj})}`;
renderer.append(str);
}
}

@ -1,6 +1,8 @@
import { snip } from '../../utils/snip';
import Renderer, { RenderOptions } from '../Renderer';
import EachBlock from '../../nodes/EachBlock';
export default function(node, renderer, options) {
export default function(node: EachBlock, renderer: Renderer, options: RenderOptions) {
const snippet = snip(node.expression);
const { start, end } = node.context_node;
@ -24,4 +26,4 @@ export default function(node, renderer, options) {
}
renderer.append('}');
}
}

@ -5,6 +5,9 @@ import Node from '../../nodes/shared/Node';
import { snip } from '../../utils/snip';
import { stringify_attribute } from '../../utils/stringify_attribute';
import { get_slot_scope } from './shared/get_slot_scope';
import Renderer, { RenderOptions } from '../Renderer';
import Element from '../../nodes/Element';
import Text from '../../nodes/Text';
// source: https://gist.github.com/ArjanSchouten/0b8574a6ad7f5065a5e7
const boolean_attributes = new Set([
@ -47,15 +50,17 @@ const boolean_attributes = new Set([
'translate'
]);
export default function(node, renderer, options) {
export default function(node: Element, renderer: Renderer, options: RenderOptions & {
slot_scopes: Map<any, any>;
}) {
let opening_tag = `<${node.name}`;
let textarea_contents; // awkward special case
const slot = node.get_static_attribute_value('slot');
const component = node.find_nearest(/InlineComponent/);
if (slot && component) {
const slot = node.attributes.find((attribute: Node) => attribute.name === 'slot');
const slot_name = slot.chunks[0].data;
const slot = node.attributes.find((attribute) => attribute.name === 'slot');
const slot_name = (slot.chunks[0] as Text).data;
const target = renderer.targets[renderer.targets.length - 1];
target.slot_stack.push(slot_name);
target.slots[slot_name] = '';
@ -160,4 +165,4 @@ export default function(node, renderer, options) {
if (!is_void(node.name)) {
renderer.append(`</${node.name}>`);
}
}
}

@ -1,7 +1,10 @@
export default function(node, renderer, options) {
import Renderer, { RenderOptions } from '../Renderer';
import Head from '../../nodes/Head';
export default function(node: Head, renderer: Renderer, options: RenderOptions) {
renderer.append('${($$result.head += `');
renderer.render(node.children, options);
renderer.append('`, "")}');
}
}

@ -1,5 +1,7 @@
import { snip } from '../../utils/snip';
import Renderer, { RenderOptions } from '../Renderer';
import RawMustacheTag from '../../nodes/RawMustacheTag';
export default function(node, renderer, options) {
export default function(node: RawMustacheTag, renderer: Renderer, options: RenderOptions) {
renderer.append('${' + snip(node.expression) + '}');
}
}

@ -1,6 +1,7 @@
import { snip } from '../../utils/snip';
export default function(node, renderer, options) {
import IfBlock from '../../nodes/IfBlock';
import Renderer, { RenderOptions } from '../Renderer';
export default function(node: IfBlock, renderer: Renderer, options: RenderOptions) {
const snippet = snip(node.expression);
renderer.append('${ ' + snippet + ' ? `');
@ -14,4 +15,4 @@ export default function(node, renderer, options) {
}
renderer.append('` }');
}
}

@ -1,14 +1,17 @@
import { escape, escape_template, stringify } from '../../utils/stringify';
import { quote_name_if_necessary } from '../../../utils/names';
import { snip } from '../../utils/snip';
import Renderer from '../Renderer';
import Renderer, { RenderOptions } from '../Renderer';
import { stringify_props } from '../../utils/stringify_props';
import { get_slot_scope } from './shared/get_slot_scope';
import { AppendTarget } from '../../../interfaces';
import InlineComponent from '../../nodes/InlineComponent';
import { INode } from '../../nodes/interfaces';
import Text from '../../nodes/Text';
function stringify_attribute(chunk: Node) {
function stringify_attribute(chunk: INode) {
if (chunk.type === 'Text') {
return escape_template(escape(chunk.data));
return escape_template(escape((chunk as Text).data));
}
return '${@escape(' + snip(chunk) + ')}';
@ -30,7 +33,7 @@ function get_attribute_value(attribute) {
return '`' + attribute.chunks.map(stringify_attribute).join('') + '`';
}
export default function(node, renderer: Renderer, options) {
export default function(node: InlineComponent, renderer: Renderer, options: RenderOptions) {
const binding_props = [];
const binding_fns = [];

@ -1,7 +1,9 @@
import { quote_prop_if_necessary } from '../../../utils/names';
import get_slot_data from '../../utils/get_slot_data';
import Renderer, { RenderOptions } from '../Renderer';
import Slot from '../../nodes/Slot';
export default function(node, renderer, options) {
export default function(node: Slot, renderer: Renderer, options: RenderOptions) {
const prop = quote_prop_if_necessary(node.slot_name);
const slot_data = get_slot_data(node.values, true);
@ -13,4 +15,4 @@ export default function(node, renderer, options) {
renderer.render(node.children, options);
renderer.append(`\`}`);
}
}

@ -1,6 +1,6 @@
import { snip } from '../../utils/snip';
export default function(node, renderer, options) {
import Renderer, { RenderOptions } from '../Renderer';
export default function(node, renderer: Renderer, options: RenderOptions) {
const snippet = snip(node.expression);
renderer.append(
@ -10,4 +10,4 @@ export default function(node, renderer, options) {
? '${' + snippet + '}'
: '${@escape(' + snippet + ')}'
);
}
}

@ -1,14 +1,17 @@
import { escape_html, escape_template, escape } from '../../utils/stringify';
import Renderer, { RenderOptions } from '../Renderer';
import Text from '../../nodes/Text';
import Element from '../../nodes/Element';
export default function(node, renderer, options) {
export default function(node: Text, renderer: Renderer, options: RenderOptions) {
let text = node.data;
if (
!node.parent ||
node.parent.type !== 'Element' ||
(node.parent.name !== 'script' && node.parent.name !== 'style')
((node.parent as Element).name !== 'script' && (node.parent as Element).name !== 'style')
) {
// unless this Text node is inside a <script> or <style> element, escape &,<,>
text = escape_html(text);
}
renderer.append(escape(escape_template(text)));
}
}

@ -1,7 +1,10 @@
export default function(node, renderer, options) {
import Renderer, { RenderOptions } from '../Renderer';
import Title from '../../nodes/Title';
export default function(node: Title, renderer: Renderer, options: RenderOptions) {
renderer.append(`<title>`);
renderer.render(node.children, options);
renderer.append(`</title>`);
}
}

@ -4,6 +4,8 @@ import { CompileOptions } from '../../interfaces';
import { stringify } from '../utils/stringify';
import Renderer from './Renderer';
import { extract_names } from '../utils/scope';
import { INode } from '../nodes/interfaces';
import Text from '../nodes/Text';
export default function ssr(
component: Component,
@ -151,10 +153,10 @@ export default function ssr(
`).trim();
}
function trim(nodes) {
function trim(nodes: INode[]) {
let start = 0;
for (; start < nodes.length; start += 1) {
const node = nodes[start];
const node = nodes[start] as Text;
if (node.type !== 'Text') break;
node.data = node.data.replace(/^\s+/, '');
@ -163,7 +165,7 @@ function trim(nodes) {
let end = nodes.length;
for (; end > start; end -= 1) {
const node = nodes[end - 1];
const node = nodes[end - 1] as Text;
if (node.type !== 'Text') break;
node.data = node.data.replace(/\s+$/, '');

@ -1,5 +1,6 @@
export default function add_to_set(a: Set<any>, b: Set<any>) {
export default function add_to_set<T>(a: Set<T>, b: Set<T> | Array<T>) {
// @ts-ignore
b.forEach(item => {
a.add(item);
});
}
}

@ -1,6 +1,7 @@
import { walk } from 'estree-walker';
import is_reference from 'is-reference';
import { Node } from '../../interfaces';
import { Node as ESTreeNode } from 'estree';
export function create_scopes(expression: Node) {
const map = new WeakMap();
@ -9,7 +10,7 @@ export function create_scopes(expression: Node) {
let scope = new Scope(null, false);
walk(expression, {
enter(node: Node, parent: Node) {
enter(node, parent) {
if (node.type === 'ImportDeclaration') {
node.specifiers.forEach(specifier => {
scope.declarations.set(specifier.local.name, specifier);
@ -25,7 +26,7 @@ export function create_scopes(expression: Node) {
if (node.id) scope.declarations.set(node.id.name, node);
}
node.params.forEach((param: Node) => {
node.params.forEach((param) => {
extract_names(param).forEach(name => {
scope.declarations.set(name, node);
});
@ -38,7 +39,7 @@ export function create_scopes(expression: Node) {
map.set(node, scope);
} else if (/(Class|Variable)Declaration/.test(node.type)) {
scope.add_declaration(node);
} else if (node.type === 'Identifier' && is_reference(node, parent)) {
} else if (node.type === 'Identifier' && is_reference(node as ESTreeNode, parent as ESTreeNode)) {
if (!scope.has(node.name) && !globals.has(node.name)) {
globals.set(node.name, node);
}

@ -1,11 +1,10 @@
import Attribute from '../nodes/Attribute';
import Node from '../nodes/shared/Node';
import { escape_template, escape } from './stringify';
import { snip } from './snip';
export function stringify_attribute(attribute: Attribute, is_ssr: boolean) {
return attribute.chunks
.map((chunk: Node) => {
.map((chunk) => {
if (chunk.type === 'Text') {
return escape_template(escape(chunk.data).replace(/"/g, '&quot;'));
}
@ -15,4 +14,4 @@ export function stringify_attribute(attribute: Attribute, is_ssr: boolean) {
: '${' + snip(chunk) + '}';
})
.join('');
}
}

@ -1,10 +1,51 @@
export interface Node {
interface BaseNode {
start: number;
end: number;
type: string;
children?: Node[];
[prop_name: string]: any;
}
export interface Text extends BaseNode {
type: 'Text',
data: string;
}
export interface MustacheTag extends BaseNode {
type: 'MustacheTag',
expresion: Node;
}
export type DirectiveType = 'Action'
| 'Animation'
| 'Binding'
| 'Class'
| 'EventHandler'
| 'Let'
| 'Ref'
| 'Transition';
interface BaseDirective extends BaseNode {
type: DirectiveType;
expression: null|Node;
name: string;
modifiers: string[]
}
export interface Transition extends BaseDirective{
type: 'Transition',
intro: boolean;
outro: boolean;
}
export type Directive = BaseDirective | Transition;
export type Node = Text
| MustacheTag
| BaseNode
| Directive
| Transition;
export interface Parser {
readonly template: string;
readonly filename?: string;
@ -92,4 +133,4 @@ export interface Var {
initialised?: boolean;
hoistable?: boolean;
subscribable?: boolean;
}
}

@ -1,9 +1,10 @@
import { parse_expression_at } from '../acorn';
import { Parser } from '../index';
import { Identifier, Node, SimpleLiteral } from 'estree';
const literals = new Map([['true', true], ['false', false], ['null', null]]);
export default function read_expression(parser: Parser) {
export default function read_expression(parser: Parser): Node {
const start = parser.index;
const name = parser.read_until(/\s*}/);
@ -17,7 +18,7 @@ export default function read_expression(parser: Parser) {
end,
value: literals.get(name),
raw: name,
};
} as SimpleLiteral;
}
return {
@ -25,7 +26,7 @@ export default function read_expression(parser: Parser) {
start,
end: start + name.length,
name,
};
} as Identifier;
}
parser.index = start;
@ -34,7 +35,7 @@ export default function read_expression(parser: Parser) {
const node = parse_expression_at(parser.template, parser.index);
parser.index = node.end;
return node;
return node as Node;
} catch (err) {
parser.acorn_error(err);
}

@ -4,7 +4,7 @@ import read_style from '../read/style';
import { decode_character_references } from '../utils/html';
import { is_void } from '../../utils/names';
import { Parser } from '../index';
import { Node } from '../../interfaces';
import { Directive, DirectiveType, Node, Text } from '../../interfaces';
import fuzzymatch from '../../utils/fuzzymatch';
import list from '../../utils/list';
@ -401,7 +401,7 @@ function read_attribute(parser: Parser, unique_names: Set<string>) {
}
if (value[0]) {
if (value.length > 1 || value[0].type === 'Text') {
if ((value as Array<any>).length > 1 || value[0].type === 'Text') {
parser.error({
code: `invalid-directive-value`,
message: `Directive value must be a JavaScript expression enclosed in curly braces`
@ -409,7 +409,7 @@ function read_attribute(parser: Parser, unique_names: Set<string>) {
}
}
const directive = {
const directive: Directive = {
start,
end,
type,
@ -445,7 +445,7 @@ function read_attribute(parser: Parser, unique_names: Set<string>) {
};
}
function get_directive_type(name) {
function get_directive_type(name: string):DirectiveType {
if (name === 'use') return 'Action';
if (name === 'animate') return 'Animation';
if (name === 'bind') return 'Binding';
@ -471,15 +471,15 @@ function read_attribute_value(parser: Parser) {
return value;
}
function read_sequence(parser: Parser, done: () => boolean) {
let current_chunk: Node = {
function read_sequence(parser: Parser, done: () => boolean): Node[] {
let current_chunk: Text = {
start: parser.index,
end: null,
type: 'Text',
data: '',
};
const chunks = [];
const chunks: Node[] = [];
while (parser.index < parser.template.length) {
const index = parser.index;

@ -74,6 +74,7 @@ export default async function preprocess(
preprocessor: PreprocessorGroup | PreprocessorGroup[],
options?: { filename?: string }
) {
// @ts-ignore todo: doublecheck
const filename = (options && options.filename) || preprocessor.filename; // legacy
const dependencies = [];

Loading…
Cancel
Save