From 7ec1bdb7126b185ef2dcf2a7105a8f5f57d6e44c Mon Sep 17 00:00:00 2001 From: Emil Tholin Date: Sat, 27 Apr 2019 21:35:32 +0200 Subject: [PATCH 1/6] Don't show 'Empty block' warnings for non-existent await branches --- src/compile/nodes/CatchBlock.ts | 6 +++-- src/compile/nodes/PendingBlock.ts | 6 +++-- src/compile/nodes/ThenBlock.ts | 6 +++-- src/parse/state/mustache.ts | 25 ++++++++++++++----- .../samples/await-then-catch/output.json | 5 +++- .../samples/await-no-catch/input.svelte | 9 +++++++ .../samples/await-no-catch/warnings.json | 1 + .../await-shorthand-no-catch/input.svelte | 7 ++++++ .../await-shorthand-no-catch/warnings.json | 1 + 9 files changed, 53 insertions(+), 13 deletions(-) create mode 100644 test/validator/samples/await-no-catch/input.svelte create mode 100644 test/validator/samples/await-no-catch/warnings.json create mode 100644 test/validator/samples/await-shorthand-no-catch/input.svelte create mode 100644 test/validator/samples/await-shorthand-no-catch/warnings.json diff --git a/src/compile/nodes/CatchBlock.ts b/src/compile/nodes/CatchBlock.ts index f728c1b850..0941e68d5b 100644 --- a/src/compile/nodes/CatchBlock.ts +++ b/src/compile/nodes/CatchBlock.ts @@ -15,6 +15,8 @@ export default class CatchBlock extends Node { this.scope.add(parent.error, parent.expression.dependencies, this); this.children = map_children(component, parent, this.scope, info.children); - this.warn_if_empty_block(); + if (!info.skip) { + this.warn_if_empty_block(); + } } -} \ No newline at end of file +} diff --git a/src/compile/nodes/PendingBlock.ts b/src/compile/nodes/PendingBlock.ts index 720442fab6..61e315481e 100644 --- a/src/compile/nodes/PendingBlock.ts +++ b/src/compile/nodes/PendingBlock.ts @@ -10,6 +10,8 @@ export default class PendingBlock extends Node { super(component, parent, scope, info); this.children = map_children(component, parent, scope, info.children); - this.warn_if_empty_block(); + if (!info.skip) { + this.warn_if_empty_block(); + } } -} \ No newline at end of file +} diff --git a/src/compile/nodes/ThenBlock.ts b/src/compile/nodes/ThenBlock.ts index 54319a7ae5..ace5cfb5c1 100644 --- a/src/compile/nodes/ThenBlock.ts +++ b/src/compile/nodes/ThenBlock.ts @@ -15,6 +15,8 @@ export default class ThenBlock extends Node { this.scope.add(parent.value, parent.expression.dependencies, this); this.children = map_children(component, parent, this.scope, info.children); - this.warn_if_empty_block(); + if (!info.skip) { + this.warn_if_empty_block(); + } } -} \ No newline at end of file +} diff --git a/src/parse/state/mustache.ts b/src/parse/state/mustache.ts index 1acae36c9c..c24b5995a2 100644 --- a/src/parse/state/mustache.ts +++ b/src/parse/state/mustache.ts @@ -172,7 +172,8 @@ export default function mustache(parser: Parser) { start, end: null, type: 'ThenBlock', - children: [] + children: [], + skip: false }; await_block.then = then_block; @@ -196,7 +197,8 @@ export default function mustache(parser: Parser) { start, end: null, type: 'CatchBlock', - children: [] + children: [], + skip: false }; await_block.catch = catch_block; @@ -235,19 +237,22 @@ export default function mustache(parser: Parser) { start: null, end: null, type: 'PendingBlock', - children: [] + children: [], + skip: true }, then: { start: null, end: null, type: 'ThenBlock', - children: [] + children: [], + skip: true }, catch: { start: null, end: null, type: 'CatchBlock', - children: [] + children: [], + skip: true }, } : { @@ -310,7 +315,15 @@ export default function mustache(parser: Parser) { parser.stack.push(block); if (type === 'AwaitBlock') { - const child_block = await_block_shorthand ? block.then : block.pending; + let child_block; + if (await_block_shorthand) { + block.then.skip = false; + child_block = block.then; + } else { + block.pending.skip = false; + child_block = block.pending; + } + child_block.start = parser.index; parser.stack.push(child_block); } diff --git a/test/parser/samples/await-then-catch/output.json b/test/parser/samples/await-then-catch/output.json index 21fc13eff9..f62ad16574 100644 --- a/test/parser/samples/await-then-catch/output.json +++ b/test/parser/samples/await-then-catch/output.json @@ -19,6 +19,7 @@ "pending": { "start": 19, "end": 39, + "skip": false, "type": "PendingBlock", "children": [ { @@ -53,6 +54,7 @@ "then": { "start": 39, "end": 88, + "skip": false, "type": "ThenBlock", "children": [ { @@ -98,6 +100,7 @@ "catch": { "start": 88, "end": 140, + "skip": false, "type": "CatchBlock", "children": [ { @@ -158,4 +161,4 @@ "css": null, "instance": null, "module": null -} \ No newline at end of file +} diff --git a/test/validator/samples/await-no-catch/input.svelte b/test/validator/samples/await-no-catch/input.svelte new file mode 100644 index 0000000000..1d332f0e32 --- /dev/null +++ b/test/validator/samples/await-no-catch/input.svelte @@ -0,0 +1,9 @@ + + +{#await promise} +

Loading

+{:then data} +

Data: {data}

+{/await} diff --git a/test/validator/samples/await-no-catch/warnings.json b/test/validator/samples/await-no-catch/warnings.json new file mode 100644 index 0000000000..fe51488c70 --- /dev/null +++ b/test/validator/samples/await-no-catch/warnings.json @@ -0,0 +1 @@ +[] diff --git a/test/validator/samples/await-shorthand-no-catch/input.svelte b/test/validator/samples/await-shorthand-no-catch/input.svelte new file mode 100644 index 0000000000..e106f8d842 --- /dev/null +++ b/test/validator/samples/await-shorthand-no-catch/input.svelte @@ -0,0 +1,7 @@ + + +{#await promise then data} +

Data: {data}

+{/await} diff --git a/test/validator/samples/await-shorthand-no-catch/warnings.json b/test/validator/samples/await-shorthand-no-catch/warnings.json new file mode 100644 index 0000000000..fe51488c70 --- /dev/null +++ b/test/validator/samples/await-shorthand-no-catch/warnings.json @@ -0,0 +1 @@ +[] From 6ebd72fc2214dc5850a3fc0327ebb07acc05d640 Mon Sep 17 00:00:00 2001 From: Emil Tholin Date: Sun, 28 Apr 2019 19:09:50 +0200 Subject: [PATCH 2/6] Check if a figcaption's first element ancestor is a figure --- src/compile/nodes/Element.ts | 16 +++++++++++++++- .../input.svelte | 10 ++++++++++ .../warnings.json | 1 + 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 test/validator/samples/a11y-figcaption-in-non-element-block/input.svelte create mode 100644 test/validator/samples/a11y-figcaption-in-non-element-block/warnings.json diff --git a/src/compile/nodes/Element.ts b/src/compile/nodes/Element.ts index b38ee5b6fb..ac2b81b3e7 100644 --- a/src/compile/nodes/Element.ts +++ b/src/compile/nodes/Element.ts @@ -224,7 +224,21 @@ export default class Element extends Node { } if (this.name === 'figcaption') { - if (this.parent.name !== 'figure') { + let { parent } = this; + let is_figure_parent = false; + + while (parent) { + if (parent.name === 'figure') { + is_figure_parent = true; + break; + } + if (parent.type === 'Element') { + break; + } + parent = parent.parent; + } + + if (!is_figure_parent) { this.component.warn(this, { code: `a11y-structure`, message: `A11y:
must be an immediate child of
` diff --git a/test/validator/samples/a11y-figcaption-in-non-element-block/input.svelte b/test/validator/samples/a11y-figcaption-in-non-element-block/input.svelte new file mode 100644 index 0000000000..9d4b6ded4d --- /dev/null +++ b/test/validator/samples/a11y-figcaption-in-non-element-block/input.svelte @@ -0,0 +1,10 @@ + + +
+ a picture of a foo + {#if caption} +
{caption}
+ {/if} +
diff --git a/test/validator/samples/a11y-figcaption-in-non-element-block/warnings.json b/test/validator/samples/a11y-figcaption-in-non-element-block/warnings.json new file mode 100644 index 0000000000..fe51488c70 --- /dev/null +++ b/test/validator/samples/a11y-figcaption-in-non-element-block/warnings.json @@ -0,0 +1 @@ +[] From 2484b9e597dc3d065dc5e7e7bec5e06879b36c27 Mon Sep 17 00:00:00 2001 From: thollander Date: Sun, 28 Apr 2019 20:20:35 +0200 Subject: [PATCH 3/6] Create a new abstraction level to handle `Block` - `AbstractBlock` contains the Block' specific rules - extends a `Node` - has a `block` and `children` - can warn if empty --- src/compile/nodes/CatchBlock.ts | 11 ++++------ src/compile/nodes/EachBlock.ts | 8 +++----- src/compile/nodes/ElseBlock.ts | 9 +++----- src/compile/nodes/IfBlock.ts | 10 +++------ src/compile/nodes/PendingBlock.ts | 9 +++----- src/compile/nodes/ThenBlock.ts | 9 +++----- src/compile/nodes/shared/AbstractBlock.ts | 25 +++++++++++++++++++++++ src/compile/nodes/shared/Node.ts | 16 ++------------- 8 files changed, 46 insertions(+), 51 deletions(-) create mode 100644 src/compile/nodes/shared/AbstractBlock.ts diff --git a/src/compile/nodes/CatchBlock.ts b/src/compile/nodes/CatchBlock.ts index f728c1b850..5171631845 100644 --- a/src/compile/nodes/CatchBlock.ts +++ b/src/compile/nodes/CatchBlock.ts @@ -1,12 +1,9 @@ -import Node from './shared/Node'; -import Block from '../render-dom/Block'; import map_children from './shared/map_children'; import TemplateScope from './shared/TemplateScope'; +import AbstractBlock from './shared/AbstractBlock'; -export default class CatchBlock extends Node { - block: Block; +export default class CatchBlock extends AbstractBlock { scope: TemplateScope; - children: Node[]; constructor(component, parent, scope, info) { super(component, parent, scope, info); @@ -14,7 +11,7 @@ export default class CatchBlock extends Node { this.scope = scope.child(); this.scope.add(parent.error, parent.expression.dependencies, this); this.children = map_children(component, parent, this.scope, info.children); - + this.warn_if_empty_block(); } -} \ No newline at end of file +} diff --git a/src/compile/nodes/EachBlock.ts b/src/compile/nodes/EachBlock.ts index ce9b63fe47..d143cdd1ec 100644 --- a/src/compile/nodes/EachBlock.ts +++ b/src/compile/nodes/EachBlock.ts @@ -1,9 +1,9 @@ import Node from './shared/Node'; import ElseBlock from './ElseBlock'; -import Block from '../render-dom/Block'; import Expression from './shared/Expression'; import map_children from './shared/map_children'; import TemplateScope from './shared/TemplateScope'; +import AbstractBlock from './shared/AbstractBlock'; import { Node as INode } from '../../interfaces'; function unpack_destructuring(contexts: Array<{ name: string, tail: string }>, node: INode, tail: string) { @@ -25,10 +25,9 @@ function unpack_destructuring(contexts: Array<{ name: string, tail: string }>, n } } -export default class EachBlock extends Node { +export default class EachBlock extends AbstractBlock { type: 'EachBlock'; - block: Block; expression: Expression; context_node: Node; @@ -41,7 +40,6 @@ export default class EachBlock extends Node { has_animation: boolean; has_binding = false; - children: Node[]; else?: ElseBlock; constructor(component, parent, scope, info) { @@ -85,7 +83,7 @@ export default class EachBlock extends Node { } } - this.warn_if_empty_block(); // TODO would be better if EachBlock, IfBlock etc extended an abstract Block class + this.warn_if_empty_block(); this.else = info.else ? new ElseBlock(component, this, this.scope, info.else) diff --git a/src/compile/nodes/ElseBlock.ts b/src/compile/nodes/ElseBlock.ts index 90886f8fdc..61c1aa5455 100644 --- a/src/compile/nodes/ElseBlock.ts +++ b/src/compile/nodes/ElseBlock.ts @@ -1,11 +1,8 @@ -import Node from './shared/Node'; -import Block from '../render-dom/Block'; import map_children from './shared/map_children'; +import AbstractBlock from './shared/AbstractBlock'; -export default class ElseBlock extends Node { +export default class ElseBlock extends AbstractBlock { type: 'ElseBlock'; - children: Node[]; - block: Block; constructor(component, parent, scope, info) { super(component, parent, scope, info); @@ -13,4 +10,4 @@ export default class ElseBlock extends Node { this.warn_if_empty_block(); } -} \ No newline at end of file +} diff --git a/src/compile/nodes/IfBlock.ts b/src/compile/nodes/IfBlock.ts index 5b79d16385..ae6aede834 100644 --- a/src/compile/nodes/IfBlock.ts +++ b/src/compile/nodes/IfBlock.ts @@ -1,17 +1,13 @@ -import Node from './shared/Node'; import ElseBlock from './ElseBlock'; -import Block from '../render-dom/Block'; import Expression from './shared/Expression'; import map_children from './shared/map_children'; +import AbstractBlock from './shared/AbstractBlock'; -export default class IfBlock extends Node { +export default class IfBlock extends AbstractBlock { type: 'IfBlock'; expression: Expression; - children: any[]; else: ElseBlock; - block: Block; - constructor(component, parent, scope, info) { super(component, parent, scope, info); @@ -24,4 +20,4 @@ export default class IfBlock extends Node { this.warn_if_empty_block(); } -} \ No newline at end of file +} diff --git a/src/compile/nodes/PendingBlock.ts b/src/compile/nodes/PendingBlock.ts index 720442fab6..688039c2ec 100644 --- a/src/compile/nodes/PendingBlock.ts +++ b/src/compile/nodes/PendingBlock.ts @@ -1,10 +1,7 @@ -import Node from './shared/Node'; -import Block from '../render-dom/Block'; import map_children from './shared/map_children'; +import AbstractBlock from './shared/AbstractBlock'; -export default class PendingBlock extends Node { - block: Block; - children: Node[]; +export default class PendingBlock extends AbstractBlock { constructor(component, parent, scope, info) { super(component, parent, scope, info); @@ -12,4 +9,4 @@ export default class PendingBlock extends Node { this.warn_if_empty_block(); } -} \ No newline at end of file +} diff --git a/src/compile/nodes/ThenBlock.ts b/src/compile/nodes/ThenBlock.ts index 54319a7ae5..d8d251e1d7 100644 --- a/src/compile/nodes/ThenBlock.ts +++ b/src/compile/nodes/ThenBlock.ts @@ -1,12 +1,9 @@ -import Node from './shared/Node'; -import Block from '../render-dom/Block'; import map_children from './shared/map_children'; import TemplateScope from './shared/TemplateScope'; +import AbstractBlock from './shared/AbstractBlock'; -export default class ThenBlock extends Node { - block: Block; +export default class ThenBlock extends AbstractBlock { scope: TemplateScope; - children: Node[]; constructor(component, parent, scope, info) { super(component, parent, scope, info); @@ -17,4 +14,4 @@ export default class ThenBlock extends Node { this.warn_if_empty_block(); } -} \ No newline at end of file +} diff --git a/src/compile/nodes/shared/AbstractBlock.ts b/src/compile/nodes/shared/AbstractBlock.ts new file mode 100644 index 0000000000..1dfebd51f0 --- /dev/null +++ b/src/compile/nodes/shared/AbstractBlock.ts @@ -0,0 +1,25 @@ +import Block from '../../render-dom/Block'; +import Component from './../../Component'; +import Node from './Node'; + +export default class AbstractBlock extends Node { + block: Block; + children: Node[]; + + constructor(component: Component, parent, scope, info: any) { + super(component, parent, scope, info); + } + + warn_if_empty_block() { + if (!this.children || this.children.length > 1) return; + + const child = this.children[0]; + + if (!child || (child.type === 'Text' && !/[^ \r\n\f\v\t]/.test(child.data))) { + this.component.warn(this, { + code: 'empty-block', + message: 'Empty block' + }); + } + } +} diff --git a/src/compile/nodes/shared/Node.ts b/src/compile/nodes/shared/Node.ts index 92c1cc40d7..b6eaf9965d 100644 --- a/src/compile/nodes/shared/Node.ts +++ b/src/compile/nodes/shared/Node.ts @@ -1,3 +1,4 @@ +import Attribute from './../Attribute'; import Component from './../../Component'; export default class Node { @@ -12,6 +13,7 @@ export default class Node { can_use_innerhtml: boolean; var: string; + attributes: Attribute[]; constructor(component: Component, parent, scope, info: any) { this.start = info.start; @@ -64,18 +66,4 @@ export default class Node { this.parent.type === type || this.parent.has_ancestor(type) : false; } - - warn_if_empty_block() { - if (!/Block$/.test(this.type) || !this.children) return; - if (this.children.length > 1) return; - - const child = this.children[0]; - - if (!child || (child.type === 'Text' && !/[^ \r\n\f\v\t]/.test(child.data))) { - this.component.warn(this, { - code: 'empty-block', - message: 'Empty block' - }); - } - } } From 194e99b350fb9b18b88bdb90f6539f4c0847962a Mon Sep 17 00:00:00 2001 From: Clemens Akens Date: Wed, 24 Apr 2019 00:41:47 +0200 Subject: [PATCH 4/6] expose parse to the public --- src/index.ts | 1 + src/interfaces.ts | 5 +++++ src/parse/index.ts | 7 +------ 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/index.ts b/src/index.ts index c80b449825..bb0194fb43 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ export { default as compile } from './compile/index'; +export { default as parse } from './parse/index'; export { default as preprocess } from './preprocess/index'; export const VERSION = '__VERSION__'; \ No newline at end of file diff --git a/src/interfaces.ts b/src/interfaces.ts index fd34f31ea1..68fef7472e 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -61,6 +61,11 @@ export interface CompileOptions { preserveWhitespace?: boolean; } +export interface ParserOptions { + filename?: string; + customElement?: boolean; +} + export interface Visitor { enter: (node: Node) => void; leave?: (node: Node) => void; diff --git a/src/parse/index.ts b/src/parse/index.ts index 39c6972213..a9c3950425 100644 --- a/src/parse/index.ts +++ b/src/parse/index.ts @@ -3,14 +3,9 @@ import fragment from './state/fragment'; import { whitespace } from '../utils/patterns'; import { reserved } from '../utils/names'; import full_char_code_at from '../utils/full_char_code_at'; -import { Node, Ast } from '../interfaces'; +import { Node, Ast, ParserOptions } from '../interfaces'; import error from '../utils/error'; -interface ParserOptions { - filename?: string; - customElement?: boolean; -} - type ParserState = (parser: Parser) => (ParserState | void); export class Parser { From df448cb36af056aa10604796f66d6b9d632c09f0 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Sat, 4 May 2019 07:29:36 -0400 Subject: [PATCH 5/6] document svelte.parse --- site/content/docs/04-compile-time.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/site/content/docs/04-compile-time.md b/site/content/docs/04-compile-time.md index 7dd1e371f6..9abeee37ef 100644 --- a/site/content/docs/04-compile-time.md +++ b/site/content/docs/04-compile-time.md @@ -153,6 +153,30 @@ compiled: { --> +### `svelte.parse` + +```js +ast: object = svelte.parse( + source: string, + options?: { + filename?: string, + customElement?: boolean + } +) +``` + +--- + +The `parse` function parses a component, returning only its abstract syntax tree. Unlike compiling with the `generate: false` option, this will not perform any validation or other analysis of the component beyond parsing it. + + +```js +const svelte = require('svelte/compiler'); + +const ast = svelte.parse(source, { filename: 'App.svelte' }); +``` + + ### `svelte.preprocess` ```js From 54a8eb9fd43f05a9187cc87979ac2fcf98a54793 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Sat, 4 May 2019 10:38:30 -0400 Subject: [PATCH 6/6] expose svelte.walk (#2661) --- site/content/docs/04-compile-time.md | 32 ++++++++++++++++++++++++++++ src/index.ts | 1 + 2 files changed, 33 insertions(+) diff --git a/site/content/docs/04-compile-time.md b/site/content/docs/04-compile-time.md index 7dd1e371f6..4d4f05f2b4 100644 --- a/site/content/docs/04-compile-time.md +++ b/site/content/docs/04-compile-time.md @@ -280,6 +280,38 @@ const { code } = svelte.preprocess(source, [ ``` +### `svelte.walk` + +```js +walk(ast: Node, { + enter(node: Node, parent: Node)?: void, + leave(node: Node, parent: Node)?: void +}) +``` + +--- + +The `walk` function provides a way to walk to abstract syntax trees generated by the parser, using the compiler's own built-in instance of [estree-walker](https://github.com/Rich-Harris/estree-walker). + +The walker takes an abstract syntax tree to walk and an object with two optional methods: `enter` and `leave`. For each node, `enter` is called (if present). Then, unless `this.skip()` is called during `enter`, each of the children are traversed, and then `leave` is called on the node. + + +```js +const svelte = require('svelte/compiler'); +svelte.walk(ast, { + enter(node, parent) { + do_something(node); + if (should_skip_children(node)) { + this.skip(); + } + }, + leave(node, parent) { + do_something_else(node); + } +}); +``` + + ### `svelte.VERSION` --- diff --git a/src/index.ts b/src/index.ts index c80b449825..81163ef962 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ export { default as compile } from './compile/index'; export { default as preprocess } from './preprocess/index'; +export { walk } from 'estree-walker'; export const VERSION = '__VERSION__'; \ No newline at end of file