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