DRY out whitespace handling

pull/7496/head
bayre 4 years ago
parent 11ada98565
commit 9200265e51

@ -18,7 +18,7 @@ import Window from './Window';
import { INode } from '../../nodes/interfaces'; import { INode } from '../../nodes/interfaces';
import Renderer from '../Renderer'; import Renderer from '../Renderer';
import Block from '../Block'; import Block from '../Block';
import { trim_start, trim_end } from '../../../utils/trim'; import { trim_text_node, trim_first_text_node } from '../../utils/trim';
import { link } from '../../../utils/link'; import { link } from '../../../utils/link';
import { Identifier } from 'estree'; import { Identifier } from 'estree';
@ -43,13 +43,6 @@ const wrappers = {
Window Window
}; };
function trimmable_at(child: INode, next_sibling: Wrapper): boolean {
// Whitespace is trimmable if one of the following is true:
// The child and its sibling share a common nearest each block (not at an each block boundary)
// The next sibling's previous node is an each block
return (next_sibling.node.find_nearest(/EachBlock/) === child.find_nearest(/EachBlock/)) || next_sibling.node.prev.type === 'EachBlock';
}
export default class FragmentWrapper { export default class FragmentWrapper {
nodes: Wrapper[]; nodes: Wrapper[];
@ -64,7 +57,8 @@ export default class FragmentWrapper {
this.nodes = []; this.nodes = [];
let last_child: Wrapper; let last_child: Wrapper;
let window_wrapper; let wrapper: Wrapper;
let window_wrapper: Window;
let i = nodes.length; let i = nodes.length;
while (i--) { while (i--) {
@ -86,20 +80,10 @@ export default class FragmentWrapper {
} }
if (child.type === 'Text') { if (child.type === 'Text') {
let { data } = child; if (child.should_skip()) continue;
// We want to remove trailing whitespace inside an element/component/block,
// *unless* there is no whitespace between this node and its next sibling
if (this.nodes.length === 0) {
const should_trim = (
next_sibling ? (next_sibling.node.type === 'Text' && /^\s/.test(next_sibling.node.data) && trimmable_at(child, next_sibling)) : !child.has_ancestor('EachBlock')
);
if (should_trim && !child.keep_space()) { const data = trim_text_node(child, this.nodes, next_sibling);
data = trim_end(data);
if (!data) continue; if (!data) continue;
}
}
// glue text nodes (which could e.g. be separated by comments) together // glue text nodes (which could e.g. be separated by comments) together
if (last_child && last_child.node.type === 'Text') { if (last_child && last_child.node.type === 'Text') {
@ -107,37 +91,20 @@ export default class FragmentWrapper {
continue; continue;
} }
const wrapper = new Text(renderer, block, parent, child, data); wrapper = new Text(renderer, block, parent, child, data);
if (wrapper.skip) continue;
this.nodes.unshift(wrapper);
link(last_child, last_child = wrapper);
} else { } else {
const Wrapper = wrappers[child.type]; const Wrapper = wrappers[child.type];
if (!Wrapper) continue; if (!Wrapper) continue;
const wrapper = new Wrapper(renderer, block, parent, child, strip_whitespace, last_child || next_sibling); wrapper = new Wrapper(renderer, block, parent, child, strip_whitespace, last_child || next_sibling);
this.nodes.unshift(wrapper); }
this.nodes.unshift(wrapper);
link(last_child, last_child = wrapper); link(last_child, last_child = wrapper);
} }
}
if (strip_whitespace) { if (strip_whitespace) {
const first = this.nodes[0] as Text; trim_first_text_node(this.nodes);
if (first && first.node.type === 'Text' && !first.node.keep_space()) {
first.data = trim_start(first.data);
if (!first.data) {
first.var = null;
this.nodes.shift();
if (this.nodes[0]) {
this.nodes[0].prev = null;
}
}
}
} }
if (window_wrapper) { if (window_wrapper) {

@ -5,7 +5,7 @@ import Renderer, { RenderOptions } from '../Renderer';
import Element from '../../nodes/Element'; import Element from '../../nodes/Element';
import { p, x } from 'code-red'; import { p, x } from 'code-red';
import Expression from '../../nodes/shared/Expression'; import Expression from '../../nodes/shared/Expression';
import remove_whitespace_children from './utils/remove_whitespace_children'; import { trim_text_nodes } from '../../utils/trim';
import fix_attribute_casing from '../../render_dom/wrappers/Element/fix_attribute_casing'; import fix_attribute_casing from '../../render_dom/wrappers/Element/fix_attribute_casing';
import { namespaces } from '../../../utils/namespaces'; import { namespaces } from '../../../utils/namespaces';
import { start_newline } from '../../../utils/patterns'; import { start_newline } from '../../../utils/patterns';
@ -13,7 +13,7 @@ import { Expression as ESExpression } from 'estree';
export default function (node: Element, renderer: Renderer, options: RenderOptions) { export default function (node: Element, renderer: Renderer, options: RenderOptions) {
const children = remove_whitespace_children(node.children, node.next); const children = trim_text_nodes(node.children, node.next);
// awkward special case // awkward special case
let node_contents; let node_contents;

@ -1,6 +1,6 @@
import Renderer, { RenderOptions } from '../Renderer'; import Renderer, { RenderOptions } from '../Renderer';
import SlotTemplate from '../../nodes/SlotTemplate'; import SlotTemplate from '../../nodes/SlotTemplate';
import remove_whitespace_children from './utils/remove_whitespace_children'; import { trim_text_nodes } from '../../utils/trim';
import { get_slot_scope } from './shared/get_slot_scope'; import { get_slot_scope } from './shared/get_slot_scope';
import InlineComponent from '../../nodes/InlineComponent'; import InlineComponent from '../../nodes/InlineComponent';
import { get_const_tags } from './shared/get_const_tags'; import { get_const_tags } from './shared/get_const_tags';
@ -9,7 +9,7 @@ export default function(node: SlotTemplate, renderer: Renderer, options: RenderO
slot_scopes: Map<any, any>; slot_scopes: Map<any, any>;
}) { }) {
const parent_inline_component = node.parent as InlineComponent; const parent_inline_component = node.parent as InlineComponent;
const children = remove_whitespace_children(node instanceof SlotTemplate ? node.children : [node], node.next); const children = trim_text_nodes(node instanceof SlotTemplate ? node.children : [node], node.next);
renderer.push(); renderer.push();
renderer.render(children, options); renderer.render(children, options);

@ -1,73 +0,0 @@
import { INode } from '../../../nodes/interfaces';
import { trim_end, trim_start } from '../../../../utils/trim';
import { link } from '../../../../utils/link';
// similar logic from `compile/render_dom/wrappers/Fragment`
// We want to remove trailing whitespace inside an element/component/block,
// *unless* there is no whitespace between this node and its next sibling
export default function remove_whitespace_children(children: INode[], next?: INode): INode[] {
const nodes: INode[] = [];
let last_child: INode;
let i = children.length;
while (i--) {
const child = children[i];
if (child.type === 'Text') {
if (child.should_skip()) {
continue;
}
let { data } = child;
if (nodes.length === 0) {
const should_trim = next
? next.type === 'Text' &&
/^\s/.test(next.data) &&
trimmable_at(child, next)
: !child.has_ancestor('EachBlock');
if (should_trim && !child.keep_space()) {
data = trim_end(data);
if (!data) continue;
}
}
// glue text nodes (which could e.g. be separated by comments) together
if (last_child && last_child.type === 'Text') {
last_child.data = data + last_child.data;
continue;
}
nodes.unshift(child);
link(last_child, last_child = child);
} else {
nodes.unshift(child);
link(last_child, last_child = child);
}
}
const first = nodes[0];
if (first && first.type === 'Text' && !first.keep_space()) {
first.data = trim_start(first.data);
if (!first.data) {
first.var = null;
nodes.shift();
if (nodes[0]) {
nodes[0].prev = null;
}
}
}
return nodes;
}
function trimmable_at(child: INode, next_sibling: INode): boolean {
// Whitespace is trimmable if one of the following is true:
// The child and its sibling share a common nearest each block (not at an each block boundary)
// The next sibling's previous node is an each block
return (
next_sibling.find_nearest(/EachBlock/) ===
child.find_nearest(/EachBlock/) || next_sibling.prev.type === 'EachBlock'
);
}

@ -0,0 +1,91 @@
import { INode } from '../nodes/interfaces';
import TextNode from '../nodes/Text';
import Wrapper from '../render_dom/wrappers/shared/Wrapper';
import TextWrapper from '../render_dom/wrappers/Text';
import { trim_end, trim_start } from '../../utils/trim';
import { link } from '../../utils/link';
function trimmable_at(child: INode, next_sibling: INode): boolean {
// Whitespace is trimmable if one of the following is true:
// The child and its sibling share a common nearest each block (not at an each block boundary)
// The next sibling's previous node is an each block
return (
next_sibling.find_nearest(/EachBlock/) === child.find_nearest(/EachBlock/) ||
next_sibling.prev.type === 'EachBlock'
);
}
export function trim_text_node(
node: TextNode,
nodes: INode[] | Wrapper[],
next: INode | Wrapper
): string {
let { data } = node;
const next_node =
next && (next as TextWrapper).node ? (next as TextWrapper).node : (next as TextNode);
// We want to remove trailing whitespace inside an element/component/block,
// *unless* there is no whitespace between this node and its next sibling
if (nodes.length === 0) {
const should_trim = next_node
? next_node.type === 'Text' && /^\s/.test(node.data) && trimmable_at(node, next_node)
: !node.has_ancestor('EachBlock');
if (should_trim && !node.keep_space()) {
data = trim_end(data);
}
}
return data;
}
export function trim_first_text_node(nodes: INode[] | Wrapper[]): void {
// bail early if there's no first node
if (!nodes[0]) return;
// determine whether we're handling a node or a wrapper
const first = (nodes[0] as TextWrapper).node ? (nodes[0] as TextWrapper) : (nodes[0] as TextNode);
const first_node = (first as TextWrapper).node
? (first as TextWrapper).node
: (first as TextNode);
// check if the (derived) node requires trimming
if (first_node.type === 'Text' && !first_node.keep_space()) {
// mutate the original data accordingly
first.data = trim_start(first.data);
if (!first.data) {
first.var = null;
nodes.shift();
if (nodes[0]) {
nodes[0].prev = null;
}
}
}
}
// a simplified version of the iterative logic in `compile/render_dom/wrappers/Fragment`
// for use in ssr handlers
export function trim_text_nodes(nodes: INode[], next: INode): INode[] {
const trimmed_nodes: INode[] = [];
let last_child: INode;
let i = nodes.length;
while (i--) {
const child = nodes[i];
if (child.type === 'Text') {
if (child.should_skip()) continue;
const data = trim_text_node(child, trimmed_nodes, next);
if (!data) continue;
// glue text nodes (which could e.g. be separated by comments) together
if (last_child && last_child.type === 'Text') {
last_child.data = data + last_child.data;
continue;
}
}
trimmed_nodes.unshift(child);
link(last_child, (last_child = child));
}
trim_first_text_node(trimmed_nodes);
return trimmed_nodes;
}
Loading…
Cancel
Save