pull/573/head
Rich-Harris 8 years ago
parent 4d5107113e
commit 78adc5b226

@ -10,15 +10,34 @@ import getIntro from './shared/utils/getIntro';
import getOutro from './shared/utils/getOutro';
import processCss from './shared/processCss';
import annotateWithScopes from '../utils/annotateWithScopes';
import { Node, Parsed, CompileOptions } from '../../interfaces';
const test = typeof global !== 'undefined' && global.__svelte_test;
export default class Generator {
source: string
name: string
// TODO all the rest...
constructor ( parsed, source: string, name: string, options ) {
parsed: Parsed;
source: string;
name: string;
options: CompileOptions;
imports: Node[];
helpers: Set<string>;
components: Set<string>;
events: Set<string>;
transitions: Set<string>;
importedComponents: Map<string, string>;
bindingGroups: string[];
expectedProperties: Set<string>;
css: string;
cssId: string;
usesRefs: boolean;
importedNames: Set<string>;
aliases: Map<string, string>;
usedNames: Set<string>;
constructor ( parsed: Parsed, source: string, name: string, options: CompileOptions ) {
this.parsed = parsed;
this.source = source;
this.name = name;
@ -46,19 +65,19 @@ export default class Generator {
// Svelte's builtin `import { get, ... } from 'svelte/shared.ts'`;
this.importedNames = new Set();
this.aliases = new Map();
this._usedNames = new Set( [ name ] );
this.usedNames = new Set( [ name ] );
}
addSourcemapLocations ( node ) {
addSourcemapLocations ( node: Node ) {
walk( node, {
enter: node => {
enter: ( node: Node ) => {
this.code.addSourcemapLocation( node.start );
this.code.addSourcemapLocation( node.end );
}
});
}
alias ( name ) {
alias ( name: string ) {
if ( !this.aliases.has( name ) ) {
this.aliases.set( name, this.getUniqueName( name ) );
}
@ -66,10 +85,10 @@ export default class Generator {
return this.aliases.get( name );
}
contextualise ( block, expression, context, isEventHandler ) {
contextualise ( block, expression: Node, context, isEventHandler ) {
this.addSourcemapLocations( expression );
const usedContexts = [];
const usedContexts: string[] = [];
const { code, helpers } = this;
const { contexts, indexes } = block;
@ -80,7 +99,7 @@ export default class Generator {
const self = this;
walk( expression, {
enter ( node, parent, key ) {
enter ( node: Node, parent: Node, key: string ) {
if ( /^Function/.test( node.type ) ) lexicalDepth += 1;
if ( node._scope ) {
@ -142,7 +161,7 @@ export default class Generator {
}
},
leave ( node ) {
leave ( node: Node ) {
if ( /^Function/.test( node.type ) ) lexicalDepth -= 1;
if ( node._scope ) scope = scope.parent;
}
@ -159,12 +178,12 @@ export default class Generator {
if ( expression._dependencies ) return expression._dependencies;
let scope = annotateWithScopes( expression );
const dependencies = [];
const dependencies: string[] = [];
const generator = this; // can't use arrow functions, because of this.skip()
walk( expression, {
enter ( node, parent ) {
enter ( node: Node, parent: Node ) {
if ( node._scope ) {
scope = node._scope;
return;
@ -184,7 +203,7 @@ export default class Generator {
}
},
leave ( node ) {
leave ( node: Node ) {
if ( node._scope ) scope = scope.parent;
}
});
@ -278,11 +297,11 @@ export default class Generator {
};
}
getUniqueName ( name ) {
getUniqueName ( name: string ) {
if ( test ) name = `${name}$`;
let alias = name;
for ( let i = 1; reservedNames.has( alias ) || this.importedNames.has( alias ) || this._usedNames.has( alias ); alias = `${name}_${i++}` );
this._usedNames.add( alias );
for ( let i = 1; reservedNames.has( alias ) || this.importedNames.has( alias ) || this.usedNames.has( alias ); alias = `${name}_${i++}` );
this.usedNames.add( alias );
return alias;
}
@ -291,13 +310,13 @@ export default class Generator {
return name => {
if ( test ) name = `${name}$`;
let alias = name;
for ( let i = 1; reservedNames.has( alias ) || this.importedNames.has( alias ) || this._usedNames.has( alias ) || localUsedNames.has( alias ); alias = `${name}_${i++}` );
for ( let i = 1; reservedNames.has( alias ) || this.importedNames.has( alias ) || this.usedNames.has( alias ) || localUsedNames.has( alias ); alias = `${name}_${i++}` );
localUsedNames.add( alias );
return alias;
};
}
parseJs ( ssr ) {
parseJs ( ssr: boolean = false ) {
const { source } = this;
const { js } = this.parsed;

@ -1,8 +1,21 @@
import CodeBuilder from '../../utils/CodeBuilder';
import deindent from '../../utils/deindent.js';
import { DomGenerator } from './index';
import { Node } from '../../interfaces';
export interface BlockOptions {
generator: DomGenerator
name: string
expression: Node
context: string
}
export default class Block {
constructor ( options ) {
generator: DomGenerator;
name: string;
expression: Node;
constructor ( options: BlockOptions ) {
this.generator = options.generator;
this.name = options.name;
this.expression = options.expression;

@ -9,9 +9,16 @@ import visit from './visit';
import shared from './shared';
import Generator from '../Generator';
import preprocess from './preprocess';
import Block from './Block';
import { Parsed, CompileOptions, Node } from '../../interfaces';
class DomGenerator extends Generator {
constructor ( parsed, source, name, options ) {
export class DomGenerator extends Generator {
blocks: Block[]
uses: Set<string>;
readonly: Set<string>;
metaBindings: string[];
constructor ( parsed: Parsed, source: string, name: string, options: CompileOptions ) {
super( parsed, source, name, options );
this.blocks = [];
this.uses = new Set();
@ -22,7 +29,7 @@ class DomGenerator extends Generator {
this.metaBindings = [];
}
helper ( name ) {
helper ( name: string ) {
if ( this.options.dev && `${name}Dev` in shared ) {
name = `${name}Dev`;
}
@ -33,7 +40,7 @@ class DomGenerator extends Generator {
}
}
export default function dom ( parsed, source, options ) {
export default function dom ( parsed: Parsed, source: string, options: CompileOptions ) {
const format = options.format || 'es';
const name = options.name || 'SvelteComponent';
@ -49,7 +56,7 @@ export default function dom ( parsed, source, options ) {
const block = preprocess( generator, state, parsed.html );
parsed.html.children.forEach( node => {
parsed.html.children.forEach( ( node: Node ) => {
visit( generator, block, state, node );
});

@ -1,12 +1,14 @@
import Block from './Block';
import { trimStart, trimEnd } from '../../utils/trim';
import { assign } from '../../shared/index.js';
import { DomGenerator } from './index';
import { Node } from '../../interfaces';
function isElseIf ( node ) {
function isElseIf ( node: Node ) {
return node && node.children.length === 1 && node.children[0].type === 'IfBlock';
}
function getChildState ( parent, child ) {
function getChildState ( parent, child = {} ) {
return assign( {}, parent, { name: null, parentNode: null }, child || {} );
}
@ -25,7 +27,7 @@ const elementsWithoutText = new Set([
]);
const preprocessors = {
MustacheTag: ( generator, block, state, node ) => {
MustacheTag: ( generator: DomGenerator, block, state, node: Node ) => {
const dependencies = block.findDependencies( node.expression );
block.addDependencies( dependencies );
@ -34,7 +36,7 @@ const preprocessors = {
});
},
RawMustacheTag: ( generator, block, state, node ) => {
RawMustacheTag: ( generator: DomGenerator, block, state, node: Node ) => {
const dependencies = block.findDependencies( node.expression );
block.addDependencies( dependencies );
@ -44,7 +46,7 @@ const preprocessors = {
node._state = getChildState( state, { basename, name });
},
Text: ( generator, block, state, node ) => {
Text: ( generator: DomGenerator, block, state, node: Node ) => {
node._state = getChildState( state );
if ( !/\S/.test( node.data ) ) {
@ -56,13 +58,13 @@ const preprocessors = {
node._state.name = block.getUniqueName( `text` );
},
IfBlock: ( generator, block, state, node ) => {
IfBlock: ( generator: DomGenerator, block, state, node: Node ) => {
const blocks = [];
let dynamic = false;
let hasIntros = false;
let hasOutros = false;
function attachBlocks ( node ) {
function attachBlocks ( node: Node ) {
const dependencies = block.findDependencies( node.expression );
block.addDependencies( dependencies );
@ -113,7 +115,7 @@ const preprocessors = {
generator.blocks.push( ...blocks );
},
EachBlock: ( generator, block, state, node ) => {
EachBlock: ( generator: DomGenerator, block, state, node: Node ) => {
const dependencies = block.findDependencies( node.expression );
block.addDependencies( dependencies );
@ -175,7 +177,7 @@ const preprocessors = {
}
},
Element: ( generator, block, state, node ) => {
Element: ( generator: DomGenerator, block, state, node: Node ) => {
const isComponent = generator.components.has( node.name ) || node.name === ':Self';
if ( isComponent ) {
@ -193,9 +195,9 @@ const preprocessors = {
});
}
node.attributes.forEach( attribute => {
node.attributes.forEach( ( attribute: Node ) => {
if ( attribute.type === 'Attribute' && attribute.value !== true ) {
attribute.value.forEach( chunk => {
attribute.value.forEach( ( chunk: Node ) => {
if ( chunk.type !== 'Text' ) {
const dependencies = block.findDependencies( chunk.expression );
block.addDependencies( dependencies );
@ -238,12 +240,12 @@ const preprocessors = {
}
};
function preprocessChildren ( generator, block, state, node, isTopLevel ) {
function preprocessChildren ( generator: DomGenerator, block, state, node: Node, isTopLevel: boolean ) {
// glue text nodes together
const cleaned = [];
let lastChild;
const cleaned: Node[] = [];
let lastChild: Node;
node.children.forEach( child => {
node.children.forEach( ( child: Node ) => {
if ( child.type === 'Comment' ) return;
if ( child.type === 'Text' && lastChild && lastChild.type === 'Text' ) {
@ -273,7 +275,7 @@ function preprocessChildren ( generator, block, state, node, isTopLevel ) {
lastChild = null;
cleaned.forEach( child => {
cleaned.forEach( ( child: Node ) => {
const preprocess = preprocessors[ child.type ];
if ( preprocess ) preprocess( generator, block, state, child );
@ -292,7 +294,7 @@ function preprocessChildren ( generator, block, state, node, isTopLevel ) {
node.children = cleaned;
}
export default function preprocess ( generator, state, node ) {
export default function preprocess ( generator: DomGenerator, state, node ) {
const block = new Block({
generator,
name: generator.alias( 'create_main_fragment' ),

@ -1,6 +1,9 @@
import visitors from './visitors/index';
import { DomGenerator } from './index';
import Block from './Block';
import { Node } from '../../interfaces';
export default function visit ( generator, block, state, node ) {
export default function visit ( generator: DomGenerator, block: Block, state, node: Node ) {
const visitor = visitors[ node.type ];
visitor( generator, block, state, node );
}

@ -1,4 +1,7 @@
export default function visitYieldTag ( generator, block, state ) {
import { DomGenerator } from '../index';
import Block from '../Block';
export default function visitYieldTag ( generator: DomGenerator, block: Block, state ) {
const parentNode = state.parentNode || block.target;
( state.parentNode ? block.builders.create : block.builders.mount ).addLine(

@ -1,6 +1,8 @@
import { Parsed, Node } from '../../interfaces';
const commentsPattern = /\/\*[\s\S]*?\*\//g;
export default function processCss ( parsed, code ) {
export default function processCss ( parsed: Parsed, code ) {
const css = parsed.css.content.styles;
const offset = parsed.css.content.start;
@ -8,9 +10,9 @@ export default function processCss ( parsed, code ) {
const keyframes = new Map();
function walkKeyframes ( node ) {
function walkKeyframes ( node: Node ) {
if ( node.type === 'Atrule' && node.name.toLowerCase() === 'keyframes' ) {
node.expression.children.forEach( expression => {
node.expression.children.forEach( ( expression: Node ) => {
if ( expression.type === 'Identifier' ) {
const newName = `svelte-${parsed.hash}-${expression.name}`;
code.overwrite( expression.start, expression.end, newName );
@ -26,8 +28,8 @@ export default function processCss ( parsed, code ) {
parsed.css.children.forEach( walkKeyframes );
function transform ( rule ) {
rule.selector.children.forEach( selector => {
function transform ( rule: Node ) {
rule.selector.children.forEach( ( selector: Node ) => {
const start = selector.start - offset;
const end = selector.end - offset;
@ -50,11 +52,11 @@ export default function processCss ( parsed, code ) {
code.overwrite( start + offset, end + offset, transformed );
});
rule.block.children.forEach( block => {
rule.block.children.forEach( ( block: Node ) => {
if ( block.type === 'Declaration' ) {
const property = block.property.toLowerCase();
if ( property === 'animation' || property === 'animation-name' ) {
block.value.children.forEach( block => {
block.value.children.forEach( ( block: Node ) => {
if ( block.type === 'Identifier' ) {
const name = block.name;
if ( keyframes.has( name ) ) {
@ -67,7 +69,7 @@ export default function processCss ( parsed, code ) {
});
}
function walk ( node ) {
function walk ( node: Node ) {
if ( node.type === 'Rule' ) {
transform( node );
} else if ( node.type === 'Atrule' && node.name.toLowerCase() === 'keyframes' ) {

@ -1,12 +1,14 @@
export default function walkHtml ( html, visitors ) {
function visit ( node ) {
import { Node } from '../../../interfaces';
export default function walkHtml ( html: Node, visitors ) {
function visit ( node: Node ) {
const visitor = visitors[ node.type ];
if ( !visitor ) throw new Error( `Not implemented: ${node.type}` );
if ( visitor.enter ) visitor.enter( node );
if ( node.children ) {
node.children.forEach( child => {
node.children.forEach( ( child: Node ) => {
visit( child );
});
}

@ -4,16 +4,16 @@ import generate from './generators/dom/index';
import generateSSR from './generators/server-side-rendering/index';
import { assign } from './shared/index.js';
import { version } from '../package.json';
import { Parsed } from './interface';
import { Parsed, CompileOptions, Warning } from './interfaces';
function normalizeOptions ( options ) {
return assign( {
function normalizeOptions ( options: CompileOptions ) :CompileOptions {
return assign({
generate: 'dom',
// a filename is necessary for sourcemap generation
filename: 'SvelteComponent.html',
onwarn: warning => {
onwarn: ( warning: Warning ) => {
if ( warning.loc ) {
console.warn( `(${warning.loc.line}:${warning.loc.column}) ${warning.message}` ); // eslint-disable-line no-console
} else {
@ -21,13 +21,13 @@ function normalizeOptions ( options ) {
}
},
onerror: error => {
onerror: ( error: Error ) => {
throw error;
}
}, options );
}
export function compile ( source, _options ) {
export function compile ( source: string, _options: CompileOptions ) {
const options = normalizeOptions( _options );
let parsed: Parsed;

@ -23,4 +23,23 @@ export interface Parsed {
html: Node;
css: Node;
js: Node;
}
export interface Warning {
loc?: {line: number, column: number, pos: number};
message: string
filename?: string
toString: () => string
}
export interface CompileOptions {
format?: string;
name?: string;
filename?: string;
dev?: boolean;
shared?: boolean | string;
onerror?: (error: Error) => void
onwarn?: (warning: Warning) => void
}

@ -3,7 +3,7 @@ import validateHtml from './html/index';
import { getLocator, Location } from 'locate-character';
import getCodeFrame from '../utils/getCodeFrame';
import CompileError from '../utils/CompileError'
import { Node, Parsed } from '../interfaces';
import { Node, Parsed, CompileOptions, Warning } from '../interfaces';
class ValidationError extends CompileError {
constructor ( message: string, template: string, index: number, filename: string ) {
@ -27,7 +27,7 @@ export class Validator {
helpers: Map<string, Node>;
transitions: Map<string, Node>;
constructor ( parsed: Parsed, source: string, options: { onwarn, name?: string, filename?: string } ) {
constructor ( parsed: Parsed, source: string, options: CompileOptions ) {
this.source = source;
this.filename = options !== undefined ? options.filename : undefined;
@ -64,7 +64,9 @@ export class Validator {
}
}
export default function validate ( parsed: Parsed, source: string, { onerror, onwarn, name, filename } ) {
export default function validate ( parsed: Parsed, source: string, options: CompileOptions ) {
const { onwarn, onerror, name, filename } = options;
try {
if ( name && !/^[a-zA-Z_$][a-zA-Z_$0-9]*$/.test( name ) ) {
const error = new Error( `options.name must be a valid identifier` );

Loading…
Cancel
Save