mirror of https://github.com/sveltejs/svelte
parent
69aeba74e2
commit
e620fbbd69
@ -0,0 +1,38 @@
|
|||||||
|
import * as assert from 'assert';
|
||||||
|
import deindent from './deindent.js';
|
||||||
|
|
||||||
|
describe( 'utils', () => {
|
||||||
|
describe( 'deindent', () => {
|
||||||
|
it( 'deindents a simple string', () => {
|
||||||
|
const deindented = deindent`
|
||||||
|
deindent me please
|
||||||
|
`;
|
||||||
|
|
||||||
|
assert.equal( deindented, `deindent me please` );
|
||||||
|
});
|
||||||
|
|
||||||
|
it( 'deindents a multiline string', () => {
|
||||||
|
const deindented = deindent`
|
||||||
|
deindent me please
|
||||||
|
and me as well
|
||||||
|
`;
|
||||||
|
|
||||||
|
assert.equal( deindented, `deindent me please\nand me as well` );
|
||||||
|
});
|
||||||
|
|
||||||
|
it( 'preserves indentation of inserted values', () => {
|
||||||
|
const insert = deindent`
|
||||||
|
line one
|
||||||
|
line two
|
||||||
|
`;
|
||||||
|
|
||||||
|
const deindented = deindent`
|
||||||
|
before
|
||||||
|
${insert}
|
||||||
|
after
|
||||||
|
`;
|
||||||
|
|
||||||
|
assert.equal( deindented, `before\n\tline one\n\tline two\nafter` );
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,25 @@
|
|||||||
|
const start = /\n(\t+)/;
|
||||||
|
|
||||||
|
export default function deindent ( strings, ...values ) {
|
||||||
|
const indentation = start.exec( strings[0] )[1];
|
||||||
|
const pattern = new RegExp( `^${indentation}`, 'gm' );
|
||||||
|
|
||||||
|
let result = strings[0].replace( start, '' ).replace( pattern, '' );
|
||||||
|
|
||||||
|
let trailingIndentation = getTrailingIndentation( result );
|
||||||
|
|
||||||
|
for ( let i = 1; i < strings.length; i += 1 ) {
|
||||||
|
const value = String( values[ i - 1 ] ).replace( /\n/g, `\n${trailingIndentation}` );
|
||||||
|
result += value + strings[i].replace( pattern, '' );
|
||||||
|
|
||||||
|
trailingIndentation = getTrailingIndentation( result );
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTrailingIndentation ( str ) {
|
||||||
|
let i = str.length;
|
||||||
|
while ( str[ i - 1 ] === ' ' || str[ i - 1 ] === '\t' ) i -= 1;
|
||||||
|
return str.slice( i, str.length );
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
import { parseExpressionAt } from 'acorn';
|
||||||
|
|
||||||
|
export default function readExpression ( parser ) {
|
||||||
|
const node = parseExpressionAt( parser.template, parser.index );
|
||||||
|
parser.index = node.end;
|
||||||
|
|
||||||
|
// TODO check it's a valid expression. probably shouldn't have
|
||||||
|
// [arrow] function expressions, etc
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
import tag from './tag.js';
|
||||||
|
import mustache from './mustache.js';
|
||||||
|
import text from './text.js';
|
||||||
|
|
||||||
|
export default function fragment ( parser ) {
|
||||||
|
parser.allowWhitespace();
|
||||||
|
|
||||||
|
if ( parser.match( '<' ) ) {
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( parser.match( '{{' ) ) {
|
||||||
|
return mustache;
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
import readExpression from '../read/expression.js';
|
||||||
|
|
||||||
|
export default function mustache ( parser ) {
|
||||||
|
const start = parser.index;
|
||||||
|
parser.index += 2;
|
||||||
|
|
||||||
|
parser.allowWhitespace();
|
||||||
|
|
||||||
|
// {{/if}} or {{/each}}
|
||||||
|
if ( parser.eat( '/' ) ) {
|
||||||
|
const current = parser.current();
|
||||||
|
let expected;
|
||||||
|
|
||||||
|
if ( current.type === 'IfBlock' ) {
|
||||||
|
expected = 'if';
|
||||||
|
} else if ( current.type === 'EachBlock' ) {
|
||||||
|
expected = 'each';
|
||||||
|
} else {
|
||||||
|
parser.error( `Unexpected block closing tag` );
|
||||||
|
}
|
||||||
|
|
||||||
|
parser.eat( expected, true );
|
||||||
|
parser.allowWhitespace();
|
||||||
|
parser.eat( '}}', true );
|
||||||
|
|
||||||
|
current.end = parser.index;
|
||||||
|
parser.stack.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO {{else}} and {{elseif expression}}
|
||||||
|
|
||||||
|
// {{#if foo}} or {{#each foo}}
|
||||||
|
else if ( parser.eat( '#' ) ) {
|
||||||
|
let type;
|
||||||
|
|
||||||
|
if ( parser.eat( 'if' ) ) {
|
||||||
|
type = 'IfBlock';
|
||||||
|
} else if ( parser.eat( 'each' ) ) {
|
||||||
|
type = 'EachBlock';
|
||||||
|
} else {
|
||||||
|
parser.error( `Expected if or each` );
|
||||||
|
}
|
||||||
|
|
||||||
|
parser.requireWhitespace();
|
||||||
|
|
||||||
|
const expression = readExpression( parser );
|
||||||
|
|
||||||
|
parser.allowWhitespace();
|
||||||
|
parser.eat( '}}', true );
|
||||||
|
|
||||||
|
const block = {
|
||||||
|
start,
|
||||||
|
end: null,
|
||||||
|
type,
|
||||||
|
expression,
|
||||||
|
children: []
|
||||||
|
};
|
||||||
|
|
||||||
|
parser.current().children.push( block );
|
||||||
|
parser.stack.push( block );
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
const expression = readExpression( parser );
|
||||||
|
|
||||||
|
parser.allowWhitespace();
|
||||||
|
parser.eat( '}}', true );
|
||||||
|
|
||||||
|
parser.current().children.push({
|
||||||
|
start,
|
||||||
|
end: parser.index,
|
||||||
|
type: 'MustacheTag',
|
||||||
|
expression
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
@ -0,0 +1,150 @@
|
|||||||
|
import readExpression from '../read/expression.js';
|
||||||
|
|
||||||
|
const validTagName = /^[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/;
|
||||||
|
const voidElementNames = /^(?:area|base|br|col|command|doctype|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/i;
|
||||||
|
|
||||||
|
export default function tag ( parser ) {
|
||||||
|
const start = parser.index++;
|
||||||
|
|
||||||
|
const isClosingTag = parser.eat( '/' );
|
||||||
|
|
||||||
|
// TODO handle cases like <li>one<li>two
|
||||||
|
|
||||||
|
const name = readTagName( parser );
|
||||||
|
|
||||||
|
if ( isClosingTag ) {
|
||||||
|
if ( !parser.eat( '>' ) ) parser.error( `Expected '>'` );
|
||||||
|
|
||||||
|
parser.current().end = parser.index;
|
||||||
|
parser.stack.pop();
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const attributes = [];
|
||||||
|
|
||||||
|
let attribute;
|
||||||
|
while ( attribute = readAttribute( parser ) ) {
|
||||||
|
attributes.push( attribute );
|
||||||
|
}
|
||||||
|
|
||||||
|
parser.allowWhitespace();
|
||||||
|
|
||||||
|
const element = {
|
||||||
|
start,
|
||||||
|
end: null, // filled in later
|
||||||
|
type: 'Element',
|
||||||
|
name,
|
||||||
|
attributes,
|
||||||
|
children: []
|
||||||
|
};
|
||||||
|
|
||||||
|
parser.current().children.push( element );
|
||||||
|
|
||||||
|
const selfClosing = parser.eat( '/' ) || voidElementNames.test( name );
|
||||||
|
|
||||||
|
if ( !parser.eat( '>' ) ) {
|
||||||
|
parser.error( `Expected >` );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( selfClosing ) {
|
||||||
|
element.end = parser.index;
|
||||||
|
} else {
|
||||||
|
// don't push self-closing elements onto the stack
|
||||||
|
parser.stack.push( element );
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readTagName ( parser ) {
|
||||||
|
const start = parser.index;
|
||||||
|
|
||||||
|
const name = parser.readUntil( /(\s|\/|>)/ );
|
||||||
|
if ( !validTagName.test( name ) ) {
|
||||||
|
parser.error( `Expected valid tag name`, start );
|
||||||
|
}
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readAttribute ( parser ) {
|
||||||
|
const name = parser.readUntil( /(\s|=|\/|>)/ );
|
||||||
|
if ( !name ) return null;
|
||||||
|
|
||||||
|
parser.allowWhitespace();
|
||||||
|
|
||||||
|
const value = parser.eat( '=' ) ? readAttributeValue( parser ) : true;
|
||||||
|
|
||||||
|
return { name, value };
|
||||||
|
}
|
||||||
|
|
||||||
|
function readAttributeValue ( parser ) {
|
||||||
|
if ( parser.eat( `'` ) ) return readQuotedAttributeValue( parser, `'` );
|
||||||
|
if ( parser.eat( `"` ) ) return readQuotedAttributeValue( parser, `"` );
|
||||||
|
|
||||||
|
parser.error( `TODO unquoted attribute values` );
|
||||||
|
}
|
||||||
|
|
||||||
|
function readQuotedAttributeValue ( parser, quoteMark ) {
|
||||||
|
let currentChunk = {
|
||||||
|
start: parser.index,
|
||||||
|
end: null,
|
||||||
|
type: 'AttributeText',
|
||||||
|
data: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
let escaped = false;
|
||||||
|
|
||||||
|
const chunks = [];
|
||||||
|
|
||||||
|
while ( parser.index < parser.template.length ) {
|
||||||
|
if ( escaped ) {
|
||||||
|
currentChunk.data += parser.template[ parser.index++ ];
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
if ( parser.match( '{{' ) ) {
|
||||||
|
const index = parser.index;
|
||||||
|
currentChunk.end = index;
|
||||||
|
|
||||||
|
if ( currentChunk.data ) {
|
||||||
|
chunks.push( currentChunk );
|
||||||
|
}
|
||||||
|
|
||||||
|
const expression = readExpression();
|
||||||
|
parser.allowWhitespace();
|
||||||
|
if ( !parser.eat( '}}' ) ) {
|
||||||
|
parser.error( `Expected }}` );
|
||||||
|
}
|
||||||
|
|
||||||
|
chunks.push({
|
||||||
|
start: index,
|
||||||
|
end: parser.index,
|
||||||
|
type: 'MustacheTag',
|
||||||
|
expression
|
||||||
|
});
|
||||||
|
|
||||||
|
currentChunk = {
|
||||||
|
start: parser.index,
|
||||||
|
end: null,
|
||||||
|
type: 'AttributeText',
|
||||||
|
data: ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
else if ( parser.match( '\\' ) ) {
|
||||||
|
escaped = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if ( parser.match( quoteMark ) ) {
|
||||||
|
if ( currentChunk.data ) {
|
||||||
|
chunks.push( currentChunk );
|
||||||
|
return chunks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parser.error( `Unexpected end of input` );
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
export default function text ( parser ) {
|
||||||
|
const start = parser.index;
|
||||||
|
|
||||||
|
let data = '';
|
||||||
|
|
||||||
|
while ( !parser.match( '<' ) && !parser.match( '{{' ) ) {
|
||||||
|
data += parser.template[ parser.index++ ];
|
||||||
|
}
|
||||||
|
|
||||||
|
parser.current().children.push({
|
||||||
|
start,
|
||||||
|
end: parser.index,
|
||||||
|
type: 'Text',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
import * as assert from 'assert';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
description: 'hello world',
|
||||||
|
data: {
|
||||||
|
name: 'world'
|
||||||
|
},
|
||||||
|
html: '<h1>Hello world!</h1>',
|
||||||
|
|
||||||
|
test ( component, target ) {
|
||||||
|
component.set({ name: 'everybody' });
|
||||||
|
assert.equal( target.innerHTML, '<h1>Hello everybody!</h1>' );
|
||||||
|
component.teardown();
|
||||||
|
assert.equal( target.innerHTML, '' );
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1 @@
|
|||||||
|
<h1>Hello {{name}}!</h1>
|
@ -0,0 +1,7 @@
|
|||||||
|
export default {
|
||||||
|
description: '{{#if}}...{{/if}} block',
|
||||||
|
data: {
|
||||||
|
visible: true
|
||||||
|
},
|
||||||
|
html: '<p>i am visible</p>'
|
||||||
|
};
|
@ -0,0 +1,3 @@
|
|||||||
|
{{#if visible}}
|
||||||
|
<p>i am visible</p>
|
||||||
|
{{/if}}
|
Loading…
Reference in new issue