Merge branch 'master' into blog-anchors-2609

pull/2675/head
Richard Harris 6 years ago
commit 16430dc6bb

@ -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` ### `svelte.preprocess`
```js ```js
@ -280,6 +304,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` ### `svelte.VERSION`
--- ---

@ -1,12 +1,9 @@
import Node from './shared/Node';
import Block from '../render-dom/Block';
import map_children from './shared/map_children'; import map_children from './shared/map_children';
import TemplateScope from './shared/TemplateScope'; import TemplateScope from './shared/TemplateScope';
import AbstractBlock from './shared/AbstractBlock';
export default class CatchBlock extends Node { export default class CatchBlock extends AbstractBlock {
block: Block;
scope: TemplateScope; scope: TemplateScope;
children: Node[];
constructor(component, parent, scope, info) { constructor(component, parent, scope, info) {
super(component, parent, scope, info); super(component, parent, scope, info);
@ -15,6 +12,8 @@ export default class CatchBlock extends Node {
this.scope.add(parent.error, parent.expression.dependencies, this); this.scope.add(parent.error, parent.expression.dependencies, this);
this.children = map_children(component, parent, this.scope, info.children); this.children = map_children(component, parent, this.scope, info.children);
this.warn_if_empty_block(); if (!info.skip) {
this.warn_if_empty_block();
}
} }
} }

@ -1,9 +1,9 @@
import Node from './shared/Node'; import Node from './shared/Node';
import ElseBlock from './ElseBlock'; import ElseBlock from './ElseBlock';
import Block from '../render-dom/Block';
import Expression from './shared/Expression'; import Expression from './shared/Expression';
import map_children from './shared/map_children'; import map_children from './shared/map_children';
import TemplateScope from './shared/TemplateScope'; import TemplateScope from './shared/TemplateScope';
import AbstractBlock from './shared/AbstractBlock';
import { Node as INode } from '../../interfaces'; import { Node as INode } from '../../interfaces';
function unpack_destructuring(contexts: Array<{ name: string, tail: string }>, node: INode, tail: string) { 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'; type: 'EachBlock';
block: Block;
expression: Expression; expression: Expression;
context_node: Node; context_node: Node;
@ -41,7 +40,6 @@ export default class EachBlock extends Node {
has_animation: boolean; has_animation: boolean;
has_binding = false; has_binding = false;
children: Node[];
else?: ElseBlock; else?: ElseBlock;
constructor(component, parent, scope, info) { 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 this.else = info.else
? new ElseBlock(component, this, this.scope, info.else) ? new ElseBlock(component, this, this.scope, info.else)

@ -224,7 +224,21 @@ export default class Element extends Node {
} }
if (this.name === 'figcaption') { 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, { this.component.warn(this, {
code: `a11y-structure`, code: `a11y-structure`,
message: `A11y: <figcaption> must be an immediate child of <figure>` message: `A11y: <figcaption> must be an immediate child of <figure>`

@ -1,11 +1,8 @@
import Node from './shared/Node';
import Block from '../render-dom/Block';
import map_children from './shared/map_children'; 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'; type: 'ElseBlock';
children: Node[];
block: Block;
constructor(component, parent, scope, info) { constructor(component, parent, scope, info) {
super(component, parent, scope, info); super(component, parent, scope, info);

@ -1,17 +1,13 @@
import Node from './shared/Node';
import ElseBlock from './ElseBlock'; import ElseBlock from './ElseBlock';
import Block from '../render-dom/Block';
import Expression from './shared/Expression'; import Expression from './shared/Expression';
import map_children from './shared/map_children'; 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'; type: 'IfBlock';
expression: Expression; expression: Expression;
children: any[];
else: ElseBlock; else: ElseBlock;
block: Block;
constructor(component, parent, scope, info) { constructor(component, parent, scope, info) {
super(component, parent, scope, info); super(component, parent, scope, info);

@ -1,15 +1,14 @@
import Node from './shared/Node';
import Block from '../render-dom/Block';
import map_children from './shared/map_children'; import map_children from './shared/map_children';
import AbstractBlock from './shared/AbstractBlock';
export default class PendingBlock extends Node { export default class PendingBlock extends AbstractBlock {
block: Block;
children: Node[];
constructor(component, parent, scope, info) { constructor(component, parent, scope, info) {
super(component, parent, scope, info); super(component, parent, scope, info);
this.children = map_children(component, parent, scope, info.children); this.children = map_children(component, parent, scope, info.children);
this.warn_if_empty_block(); if (!info.skip) {
this.warn_if_empty_block();
}
} }
} }

@ -1,12 +1,9 @@
import Node from './shared/Node';
import Block from '../render-dom/Block';
import map_children from './shared/map_children'; import map_children from './shared/map_children';
import TemplateScope from './shared/TemplateScope'; import TemplateScope from './shared/TemplateScope';
import AbstractBlock from './shared/AbstractBlock';
export default class ThenBlock extends Node { export default class ThenBlock extends AbstractBlock {
block: Block;
scope: TemplateScope; scope: TemplateScope;
children: Node[];
constructor(component, parent, scope, info) { constructor(component, parent, scope, info) {
super(component, parent, scope, info); super(component, parent, scope, info);
@ -15,6 +12,8 @@ export default class ThenBlock extends Node {
this.scope.add(parent.value, parent.expression.dependencies, this); this.scope.add(parent.value, parent.expression.dependencies, this);
this.children = map_children(component, parent, this.scope, info.children); this.children = map_children(component, parent, this.scope, info.children);
this.warn_if_empty_block(); if (!info.skip) {
this.warn_if_empty_block();
}
} }
} }

@ -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'
});
}
}
}

@ -1,3 +1,4 @@
import Attribute from './../Attribute';
import Component from './../../Component'; import Component from './../../Component';
export default class Node { export default class Node {
@ -12,6 +13,7 @@ export default class Node {
can_use_innerhtml: boolean; can_use_innerhtml: boolean;
var: string; var: string;
attributes: Attribute[];
constructor(component: Component, parent, scope, info: any) { constructor(component: Component, parent, scope, info: any) {
this.start = info.start; this.start = info.start;
@ -64,18 +66,4 @@ export default class Node {
this.parent.type === type || this.parent.has_ancestor(type) : this.parent.type === type || this.parent.has_ancestor(type) :
false; 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'
});
}
}
} }

@ -1,4 +1,6 @@
export { default as compile } from './compile/index'; export { default as compile } from './compile/index';
export { default as parse } from './parse/index';
export { default as preprocess } from './preprocess/index'; export { default as preprocess } from './preprocess/index';
export { walk } from 'estree-walker';
export const VERSION = '__VERSION__'; export const VERSION = '__VERSION__';

@ -61,6 +61,11 @@ export interface CompileOptions {
preserveWhitespace?: boolean; preserveWhitespace?: boolean;
} }
export interface ParserOptions {
filename?: string;
customElement?: boolean;
}
export interface Visitor { export interface Visitor {
enter: (node: Node) => void; enter: (node: Node) => void;
leave?: (node: Node) => void; leave?: (node: Node) => void;

@ -3,14 +3,9 @@ import fragment from './state/fragment';
import { whitespace } from '../utils/patterns'; import { whitespace } from '../utils/patterns';
import { reserved } from '../utils/names'; import { reserved } from '../utils/names';
import full_char_code_at from '../utils/full_char_code_at'; 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'; import error from '../utils/error';
interface ParserOptions {
filename?: string;
customElement?: boolean;
}
type ParserState = (parser: Parser) => (ParserState | void); type ParserState = (parser: Parser) => (ParserState | void);
export class Parser { export class Parser {

@ -172,7 +172,8 @@ export default function mustache(parser: Parser) {
start, start,
end: null, end: null,
type: 'ThenBlock', type: 'ThenBlock',
children: [] children: [],
skip: false
}; };
await_block.then = then_block; await_block.then = then_block;
@ -196,7 +197,8 @@ export default function mustache(parser: Parser) {
start, start,
end: null, end: null,
type: 'CatchBlock', type: 'CatchBlock',
children: [] children: [],
skip: false
}; };
await_block.catch = catch_block; await_block.catch = catch_block;
@ -235,19 +237,22 @@ export default function mustache(parser: Parser) {
start: null, start: null,
end: null, end: null,
type: 'PendingBlock', type: 'PendingBlock',
children: [] children: [],
skip: true
}, },
then: { then: {
start: null, start: null,
end: null, end: null,
type: 'ThenBlock', type: 'ThenBlock',
children: [] children: [],
skip: true
}, },
catch: { catch: {
start: null, start: null,
end: null, end: null,
type: 'CatchBlock', type: 'CatchBlock',
children: [] children: [],
skip: true
}, },
} : } :
{ {
@ -303,7 +308,15 @@ export default function mustache(parser: Parser) {
parser.stack.push(block); parser.stack.push(block);
if (type === 'AwaitBlock') { 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; child_block.start = parser.index;
parser.stack.push(child_block); parser.stack.push(child_block);
} }

@ -19,6 +19,7 @@
"pending": { "pending": {
"start": 19, "start": 19,
"end": 39, "end": 39,
"skip": false,
"type": "PendingBlock", "type": "PendingBlock",
"children": [ "children": [
{ {
@ -53,6 +54,7 @@
"then": { "then": {
"start": 39, "start": 39,
"end": 88, "end": 88,
"skip": false,
"type": "ThenBlock", "type": "ThenBlock",
"children": [ "children": [
{ {
@ -98,6 +100,7 @@
"catch": { "catch": {
"start": 88, "start": 88,
"end": 140, "end": 140,
"skip": false,
"type": "CatchBlock", "type": "CatchBlock",
"children": [ "children": [
{ {

@ -0,0 +1,10 @@
<script>
let caption = 'a foo in its natural habitat';
</script>
<figure>
<img src='foo.jpg' alt='a picture of a foo'>
{#if caption}
<figcaption>{caption}</figcaption>
{/if}
</figure>

@ -0,0 +1,9 @@
<script>
let promise;
</script>
{#await promise}
<p>Loading</p>
{:then data}
<p>Data: {data}</p>
{/await}

@ -0,0 +1,7 @@
<script>
let promise;
</script>
{#await promise then data}
<p>Data: {data}</p>
{/await}
Loading…
Cancel
Save