diff --git a/src/compile/nodes/DebugTag.ts b/src/compile/nodes/DebugTag.ts new file mode 100644 index 0000000000..7bd2742c4f --- /dev/null +++ b/src/compile/nodes/DebugTag.ts @@ -0,0 +1,44 @@ +import Node from './shared/Node'; +import Tag from './shared/Tag'; +import Block from '../dom/Block'; +import Expression from './shared/Expression'; +import deindent from '../../utils/deindent'; + +export default class DebugTag extends Node { + expression: Expression; + + constructor(compiler, parent, scope, info) { + super(compiler, parent, scope, info); + if (info.expression !== null) + // Debug when expression nodes change + this.expression = new Expression(compiler, parent, scope, info.expression); + else + // "Debug all" + this.expression = info.expression + } + + build( + block: Block, + parentNode: string, + parentNodes: string, + ) { + // Debug all + if (this.expression === null) { + block.builders.create.addLine('debugger;'); + block.builders.update.addLine('debugger;'); + } else { + const { dependencies } = this.expression; + + const condition = [...dependencies].map(d => `changed.${d}`).join(' || '); + + const identifiers = [...dependencies].join(', '); + + block.builders.update.addBlock(deindent` + if (${condition}) { + const { ${identifiers} } = ctx; + debugger; + } + `); + } + } +} \ No newline at end of file diff --git a/src/compile/nodes/Element.ts b/src/compile/nodes/Element.ts index 7ab49b9768..11eea89107 100644 --- a/src/compile/nodes/Element.ts +++ b/src/compile/nodes/Element.ts @@ -21,7 +21,45 @@ import mapChildren from './shared/mapChildren'; import { dimensions } from '../../utils/patterns'; // source: https://gist.github.com/ArjanSchouten/0b8574a6ad7f5065a5e7 -const booleanAttributes = new Set('async autocomplete autofocus autoplay border challenge checked compact contenteditable controls default defer disabled formnovalidate frameborder hidden indeterminate ismap loop multiple muted nohref noresize noshade novalidate nowrap open readonly required reversed scoped scrolling seamless selected sortable spellcheck translate'.split(' ')); +const booleanAttributes = new Set([ + 'async', + 'autocomplete', + 'autofocus', + 'autoplay', + 'border', + 'challenge', + 'checked', + 'compact', + 'contenteditable', + 'controls', + 'default', + 'defer', + 'disabled', + 'formnovalidate', + 'frameborder', + 'hidden', + 'indeterminate', + 'ismap', + 'loop', + 'multiple', + 'muted', + 'nohref', + 'noresize', + 'noshade', + 'novalidate', + 'nowrap', + 'open', + 'readonly', + 'required', + 'reversed', + 'scoped', + 'scrolling', + 'seamless', + 'selected', + 'sortable', + 'spellcheck', + 'translate' +]); export default class Element extends Node { type: 'Element'; diff --git a/src/compile/nodes/shared/mapChildren.ts b/src/compile/nodes/shared/mapChildren.ts index 0a0a111322..9cd238644c 100644 --- a/src/compile/nodes/shared/mapChildren.ts +++ b/src/compile/nodes/shared/mapChildren.ts @@ -7,6 +7,7 @@ import Head from '../Head'; import IfBlock from '../IfBlock'; import MustacheTag from '../MustacheTag'; import RawMustacheTag from '../RawMustacheTag'; +import DebugTag from '../DebugTag'; import Slot from '../Slot'; import Text from '../Text'; import Title from '../Title'; @@ -24,6 +25,7 @@ function getConstructor(type): typeof Node { case 'IfBlock': return IfBlock; case 'MustacheTag': return MustacheTag; case 'RawMustacheTag': return RawMustacheTag; + case 'DebugTag': return DebugTag; case 'Slot': return Slot; case 'Text': return Text; case 'Title': return Title; diff --git a/src/parse/state/mustache.ts b/src/parse/state/mustache.ts index cbac76e6a1..8450074c66 100644 --- a/src/parse/state/mustache.ts +++ b/src/parse/state/mustache.ts @@ -313,6 +313,24 @@ export default function mustache(parser: Parser) { type: 'RawMustacheTag', expression, }); + } else if (parser.eat('@debug')) { + let expression; + + // Implies {@debug} which indicates "debug all" + if (/\s*}/.test(parser.template[parser.index])) + expression = null; + else + expression = readExpression(parser); + + parser.allowWhitespace(); + parser.eat('}', true); + + parser.current().children.push({ + start, + end: parser.index, + type: 'DebugTag', + expression, + }); } else { const expression = readExpression(parser); diff --git a/test/js/samples/debug-empty/expected-bundle.js b/test/js/samples/debug-empty/expected-bundle.js new file mode 100644 index 0000000000..ba3b1dda59 --- /dev/null +++ b/test/js/samples/debug-empty/expected-bundle.js @@ -0,0 +1,200 @@ +function noop() {} + +function assign(tar, src) { + for (var k in src) tar[k] = src[k]; + return tar; +} + +function append(target, node) { + target.appendChild(node); +} + +function insert(target, node, anchor) { + target.insertBefore(node, anchor); +} + +function detachNode(node) { + node.parentNode.removeChild(node); +} + +function createElement(name) { + return document.createElement(name); +} + +function createText(data) { + return document.createTextNode(data); +} + +function setData(text, data) { + text.data = '' + data; +} + +function blankObject() { + return Object.create(null); +} + +function destroy(detach) { + this.destroy = noop; + this.fire('destroy'); + this.set = noop; + + this._fragment.d(detach !== false); + this._fragment = null; + this._state = {}; +} + +function _differs(a, b) { + return a != a ? b == b : a !== b || ((a && typeof a === 'object') || typeof a === 'function'); +} + +function fire(eventName, data) { + var handlers = + eventName in this._handlers && this._handlers[eventName].slice(); + if (!handlers) return; + + for (var i = 0; i < handlers.length; i += 1) { + var handler = handlers[i]; + + if (!handler.__calling) { + try { + handler.__calling = true; + handler.call(this, data); + } finally { + handler.__calling = false; + } + } + } +} + +function get() { + return this._state; +} + +function init(component, options) { + component._handlers = blankObject(); + component._bind = options._bind; + + component.options = options; + component.root = options.root || component; + component.store = options.store || component.root.store; +} + +function on(eventName, handler) { + var handlers = this._handlers[eventName] || (this._handlers[eventName] = []); + handlers.push(handler); + + return { + cancel: function() { + var index = handlers.indexOf(handler); + if (~index) handlers.splice(index, 1); + } + }; +} + +function set(newState) { + this._set(assign({}, newState)); + if (this.root._lock) return; + this.root._lock = true; + callAll(this.root._beforecreate); + callAll(this.root._oncreate); + callAll(this.root._aftercreate); + this.root._lock = false; +} + +function _set(newState) { + var oldState = this._state, + changed = {}, + dirty = false; + + for (var key in newState) { + if (this._differs(newState[key], oldState[key])) changed[key] = dirty = true; + } + if (!dirty) return; + + this._state = assign(assign({}, oldState), newState); + this._recompute(changed, this._state); + if (this._bind) this._bind(changed, this._state); + + if (this._fragment) { + this.fire("state", { changed: changed, current: this._state, previous: oldState }); + this._fragment.p(changed, this._state); + this.fire("update", { changed: changed, current: this._state, previous: oldState }); + } +} + +function callAll(fns) { + while (fns && fns.length) fns.shift()(); +} + +function _mount(target, anchor) { + this._fragment[this._fragment.i ? 'i' : 'm'](target, anchor || null); +} + +var proto = { + destroy, + get, + fire, + on, + set, + _recompute: noop, + _set, + _mount, + _differs +}; + +/* generated by Svelte vX.Y.Z */ + +function create_main_fragment(component, ctx) { + var h1, text, text_1, text_2, text_3; + + return { + c() { + h1 = createElement("h1"); + text = createText("Hello "); + text_1 = createText(ctx.name); + text_2 = createText("!"); + text_3 = createText("\n"); + debugger; + }, + + m(target, anchor) { + insert(target, h1, anchor); + append(h1, text); + append(h1, text_1); + append(h1, text_2); + insert(target, text_3, anchor); + }, + + p(changed, ctx) { + if (changed.name) { + setData(text_1, ctx.name); + } + + debugger; + }, + + d(detach) { + if (detach) { + detachNode(h1); + detachNode(text_3); + } + } + }; +} + +function SvelteComponent(options) { + init(this, options); + this._state = assign({}, options.data); + this._intro = true; + + this._fragment = create_main_fragment(this, this._state); + + if (options.target) { + this._fragment.c(); + this._mount(options.target, options.anchor); + } +} + +assign(SvelteComponent.prototype, proto); + +export default SvelteComponent; diff --git a/test/js/samples/debug-empty/expected.js b/test/js/samples/debug-empty/expected.js new file mode 100644 index 0000000000..592f3a7a2b --- /dev/null +++ b/test/js/samples/debug-empty/expected.js @@ -0,0 +1,56 @@ +/* generated by Svelte vX.Y.Z */ +import { append, assign, createElement, createText, detachNode, init, insert, proto, setData } from "svelte/shared.js"; + +function create_main_fragment(component, ctx) { + var h1, text, text_1, text_2, text_3; + + return { + c() { + h1 = createElement("h1"); + text = createText("Hello "); + text_1 = createText(ctx.name); + text_2 = createText("!"); + text_3 = createText("\n"); + debugger; + }, + + m(target, anchor) { + insert(target, h1, anchor); + append(h1, text); + append(h1, text_1); + append(h1, text_2); + insert(target, text_3, anchor); + }, + + p(changed, ctx) { + if (changed.name) { + setData(text_1, ctx.name); + } + + debugger; + }, + + d(detach) { + if (detach) { + detachNode(h1); + detachNode(text_3); + } + } + }; +} + +function SvelteComponent(options) { + init(this, options); + this._state = assign({}, options.data); + this._intro = true; + + this._fragment = create_main_fragment(this, this._state); + + if (options.target) { + this._fragment.c(); + this._mount(options.target, options.anchor); + } +} + +assign(SvelteComponent.prototype, proto); +export default SvelteComponent; \ No newline at end of file diff --git a/test/js/samples/debug-empty/input.html b/test/js/samples/debug-empty/input.html new file mode 100644 index 0000000000..11da4f6d15 --- /dev/null +++ b/test/js/samples/debug-empty/input.html @@ -0,0 +1,2 @@ +
foo: {foo}
\ No newline at end of file diff --git a/test/validator/samples/debug-invalid/errors.json b/test/validator/samples/debug-invalid/errors.json new file mode 100644 index 0000000000..1dc4507a39 --- /dev/null +++ b/test/validator/samples/debug-invalid/errors.json @@ -0,0 +1,15 @@ +[{ + "code": "invalid-debug", + "message": "{@debug ...} arguments must be identifers, not arbitrary expressions", + "start": { + "line": 2, + "column": 9, + "character": 13 + }, + "end": { + "line": 2, + "column": 14, + "character": 18 + }, + "pos": 13 +}] \ No newline at end of file diff --git a/test/validator/samples/debug-invalid/input.html b/test/validator/samples/debug-invalid/input.html new file mode 100644 index 0000000000..5c89e7049d --- /dev/null +++ b/test/validator/samples/debug-invalid/input.html @@ -0,0 +1,4 @@ ++ {@debug a + b} + {a + b} +
\ No newline at end of file