Skip to content

Commit

Permalink
Merge branch 'master' into gh-592
Browse files Browse the repository at this point in the history
  • Loading branch information
Rich-Harris authored May 28, 2017
2 parents c4b68ca + 31d8ef6 commit f511962
Show file tree
Hide file tree
Showing 28 changed files with 326 additions and 98 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ coverage.lcov
test/sourcemaps/samples/*/output.js
test/sourcemaps/samples/*/output.js.map
_actual.*
_actual-bundle.*
_actual-bundle.*
src/generators/dom/shared.ts
10 changes: 5 additions & 5 deletions src/generators/Generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,7 @@ export default class Generator {
const { name } = flattenReference( node );
if ( scope.has( name ) ) return;

if ( parent && parent.type === 'CallExpression' && node === parent.callee && helpers.has( name ) ) {
code.prependRight( node.start, `${self.alias( 'template' )}.helpers.` );
}

else if ( name === 'event' && isEventHandler ) {
if ( name === 'event' && isEventHandler ) {
// noop
}

Expand All @@ -135,6 +131,10 @@ export default class Generator {
if ( !~usedContexts.indexOf( name ) ) usedContexts.push( name );
}

else if ( helpers.has( name ) ) {
code.prependRight( node.start, `${self.alias( 'template' )}.helpers.` );
}

else if ( indexes.has( name ) ) {
const context = indexes.get( name );
if ( !~usedContexts.indexOf( context ) ) usedContexts.push( context );
Expand Down
35 changes: 0 additions & 35 deletions src/generators/dom/shared.ts

This file was deleted.

14 changes: 14 additions & 0 deletions src/generators/dom/visitors/Element/Element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,20 @@ export default function visitElement ( generator: DomGenerator, block: Block, st
}

if ( node.name !== 'select' ) {
if ( node.name === 'textarea' ) {
// this is an egregious hack, but it's the easiest way to get <textarea>
// children treated the same way as a value attribute
if ( node.children.length > 0 ) {
node.attributes.push({
type: 'Attribute',
name: 'value',
value: node.children
});

node.children = [];
}
}

// <select> value attributes are an annoying special case — it must be handled
// *after* its children have been updated
visitAttributesAndAddProps();
Expand Down
2 changes: 1 addition & 1 deletion src/generators/dom/visitors/Element/lookup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ const lookup = {
title: {},
type: { appliesTo: [ 'button', 'input', 'command', 'embed', 'object', 'script', 'source', 'style', 'menu' ] },
usemap: { propertyName: 'useMap', appliesTo: [ 'img', 'input', 'object' ] },
value: { appliesTo: [ 'button', 'option', 'input', 'li', 'meter', 'progress', 'param', 'select' ] },
value: { appliesTo: [ 'button', 'option', 'input', 'li', 'meter', 'progress', 'param', 'select', 'textarea' ] },
width: { appliesTo: [ 'canvas', 'embed', 'iframe', 'img', 'input', 'object', 'video' ] },
wrap: { appliesTo: [ 'textarea' ] }
};
Expand Down
45 changes: 29 additions & 16 deletions src/generators/server-side-rendering/visitors/Element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@ const meta = {
':Window': visitWindow
};

function stringifyAttributeValue ( block: Block, chunks: Node[] ) {
return chunks.map( ( chunk: Node ) => {
if ( chunk.type === 'Text' ) {
return chunk.data;
}

const { snippet } = block.contextualise( chunk.expression );
return '${' + snippet + '}';
}).join( '' )
}

export default function visitElement ( generator: SsrGenerator, block: Block, node: Node ) {
if ( node.name in meta ) {
return meta[ node.name ]( generator, block, node );
Expand All @@ -21,24 +32,22 @@ export default function visitElement ( generator: SsrGenerator, block: Block, no
}

let openingTag = `<${node.name}`;
let textareaContents; // awkward special case

node.attributes.forEach( ( attribute: Node ) => {
if ( attribute.type !== 'Attribute' ) return;

let str = ` ${attribute.name}`;
if ( attribute.name === 'value' && node.name === 'textarea' ) {
textareaContents = stringifyAttributeValue( block, attribute.value );
} else {
let str = ` ${attribute.name}`;

if ( attribute.value !== true ) {
str += `="` + attribute.value.map( ( chunk: Node ) => {
if ( chunk.type === 'Text' ) {
return chunk.data;
}
if ( attribute.value !== true ) {
str += `="${stringifyAttributeValue( block, attribute.value )}"`;
}

const { snippet } = block.contextualise( chunk.expression );
return '${' + snippet + '}';
}).join( '' ) + `"`;
openingTag += str;
}

openingTag += str;
});

if ( generator.cssId && !generator.elementDepth ) {
Expand All @@ -49,13 +58,17 @@ export default function visitElement ( generator: SsrGenerator, block: Block, no

generator.append( openingTag );

generator.elementDepth += 1;
if ( node.name === 'textarea' && textareaContents !== undefined ) {
generator.append( textareaContents );
} else {
generator.elementDepth += 1;

node.children.forEach( ( child: Node ) => {
visit( generator, block, child );
});
node.children.forEach( ( child: Node ) => {
visit( generator, block, child );
});

generator.elementDepth -= 1;
generator.elementDepth -= 1;
}

if ( !isVoidElementName( node.name ) ) {
generator.append( `</${node.name}>` );
Expand Down
91 changes: 52 additions & 39 deletions src/parse/state/tag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { Parser } from '../index';
import { Node } from '../../interfaces';

const validTagName = /^\!?[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/;
const invalidUnquotedAttributeCharacters = /[\s"'=<>\/`]/;

const SELF = ':Self';

Expand Down Expand Up @@ -181,6 +180,11 @@ export default function tag ( parser: Parser ) {

if ( selfClosing ) {
element.end = parser.index;
} else if ( name === 'textarea' ) {
// special case
element.children = readSequence( parser, () => parser.template.slice( parser.index, parser.index + 11 ) === '</textarea>' );
parser.read( /<\/textarea>/ );
element.end = parser.index;
} else {
// don't push self-closing elements onto the stack
parser.stack.push( element );
Expand Down Expand Up @@ -280,28 +284,66 @@ function readAttribute ( parser: Parser, uniqueNames ) {
}

function readAttributeValue ( parser: Parser ) {
let quoteMark;
const quoteMark = (
parser.eat( `'` ) ? `'` :
parser.eat( `"` ) ? `"` :
null
);

const regex = (
quoteMark === `'` ? /'/ :
quoteMark === `"` ? /"/ :
/[\s"'=<>\/`]/
);

const value = readSequence( parser, () => regex.test( parser.template[ parser.index ] ) );

if ( quoteMark ) parser.index += 1;
return value;
}

if ( parser.eat( `'` ) ) quoteMark = `'`;
if ( parser.eat( `"` ) ) quoteMark = `"`;
function getShorthandValue ( start: number, name: string ) {
const end = start + name.length;

return [{
type: 'AttributeShorthand',
start,
end,
expression: {
type: 'Identifier',
start,
end,
name
}
}];
}

function readSequence ( parser: Parser, done: () => boolean ) {
let currentChunk: Node = {
start: parser.index,
end: null,
type: 'Text',
data: ''
};

const done = quoteMark ?
char => char === quoteMark :
char => invalidUnquotedAttributeCharacters.test( char );

const chunks = [];

while ( parser.index < parser.template.length ) {
const index = parser.index;

if ( parser.eat( '{{' ) ) {
if ( done() ) {
currentChunk.end = parser.index;

if ( currentChunk.data ) chunks.push( currentChunk );

chunks.forEach( chunk => {
if ( chunk.type === 'Text' ) chunk.data = decodeCharacterReferences( chunk.data );
});

return chunks;
}

else if ( parser.eat( '{{' ) ) {
if ( currentChunk.data ) {
currentChunk.end = index;
chunks.push( currentChunk );
Expand All @@ -328,39 +370,10 @@ function readAttributeValue ( parser: Parser ) {
};
}

else if ( done( parser.template[ parser.index ] ) ) {
currentChunk.end = parser.index;
if ( quoteMark ) parser.index += 1;

if ( currentChunk.data ) chunks.push( currentChunk );

chunks.forEach( chunk => {
if ( chunk.type === 'Text' ) chunk.data = decodeCharacterReferences( chunk.data );
});

return chunks;
}

else {
currentChunk.data += parser.template[ parser.index++ ];
}
}

parser.error( `Unexpected end of input` );
}

function getShorthandValue ( start: number, name: string ) {
const end = start + name.length;

return [{
type: 'AttributeShorthand',
start,
end,
expression: {
type: 'Identifier',
start,
end,
name
}
}];
}
}
11 changes: 10 additions & 1 deletion src/shared/transitions.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,21 @@ export function wrapTransition ( node, fn, params, intro, outgroup ) {
var obj = fn( node, params );
var duration = obj.duration || 300;
var ease = obj.easing || linear;
var cssText;

// TODO share <style> tag between all transitions?
if ( obj.css ) {
var style = document.createElement( 'style' );
}

if ( intro && obj.tick ) obj.tick( 0 );
if ( intro ) {
if ( obj.css && obj.delay ) {
cssText = node.style.cssText;
node.style.cssText += obj.css( 0 );
}

if ( obj.tick ) obj.tick( 0 );
}

return {
t: intro ? 0 : 1,
Expand Down Expand Up @@ -70,6 +78,7 @@ export function wrapTransition ( node, fn, params, intro, outgroup ) {
program.end = program.start + program.duration;

if ( obj.css ) {
if ( obj.delay ) node.style.cssText = cssText;
generateKeyframes( program.a, program.b, program.delta, program.duration, ease, obj.css, node, style );
}

Expand Down
11 changes: 11 additions & 0 deletions src/validate/html/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,17 @@ export default function validateHtml ( validator: Validator, html: Node ) {
elementDepth += 1;

validateElement( validator, node );
} else if ( node.type === 'EachBlock' ) {
if ( validator.helpers.has( node.context ) ) {
let c = node.expression.end;

// find start of context
while ( /\s/.test( validator.source[c] ) ) c += 1;
c += 2;
while ( /\s/.test( validator.source[c] ) ) c += 1;

validator.warn( `Context clashes with a helper. Rename one or the other to eliminate any ambiguity`, c );
}
}

if ( node.children ) {
Expand Down
8 changes: 8 additions & 0 deletions src/validate/html/validateElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,14 @@ export default function validateElement ( validator: Validator, node: Node ) {
validator.error( `Missing transition '${attribute.name}'`, attribute.start );
}
}

else if ( attribute.type === 'Attribute' ) {
if ( attribute.name === 'value' && node.name === 'textarea' ) {
if ( node.children.length ) {
validator.error( `A <textarea> can have either a value attribute or (equivalently) child content, but not both`, attribute.start );
}
}
}
});
}

Expand Down
3 changes: 3 additions & 0 deletions test/parser/samples/textarea-children/input.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<textarea>
<p>not actually an element. {{foo}}</p>
</textarea>
Loading

0 comments on commit f511962

Please sign in to comment.