diff --git a/src/generators/Generator.ts b/src/generators/Generator.ts index 18135a3045..9e6d2c0a76 100644 --- a/src/generators/Generator.ts +++ b/src/generators/Generator.ts @@ -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; + components: Set; + events: Set; + transitions: Set; + importedComponents: Map; + + bindingGroups: string[]; + expectedProperties: Set; + css: string; + cssId: string; + usesRefs: boolean; + + importedNames: Set; + aliases: Map; + usedNames: Set; + + 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; diff --git a/src/generators/dom/Block.ts b/src/generators/dom/Block.ts index b471b7f00f..0711d76344 100644 --- a/src/generators/dom/Block.ts +++ b/src/generators/dom/Block.ts @@ -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; diff --git a/src/generators/dom/index.ts b/src/generators/dom/index.ts index 4614712b90..d2d02ff14d 100644 --- a/src/generators/dom/index.ts +++ b/src/generators/dom/index.ts @@ -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; + readonly: Set; + 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 ); }); diff --git a/src/generators/dom/preprocess.ts b/src/generators/dom/preprocess.ts index 7d26c5a2c0..1ea44a6855 100644 --- a/src/generators/dom/preprocess.ts +++ b/src/generators/dom/preprocess.ts @@ -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' ), diff --git a/src/generators/dom/visit.ts b/src/generators/dom/visit.ts index e37a8c7fd8..1443a32049 100644 --- a/src/generators/dom/visit.ts +++ b/src/generators/dom/visit.ts @@ -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 ); } \ No newline at end of file diff --git a/src/generators/dom/visitors/YieldTag.ts b/src/generators/dom/visitors/YieldTag.ts index 81dcb3ad53..ebebb78557 100644 --- a/src/generators/dom/visitors/YieldTag.ts +++ b/src/generators/dom/visitors/YieldTag.ts @@ -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( diff --git a/src/generators/shared/processCss.ts b/src/generators/shared/processCss.ts index 91ca055d31..b83630d756 100644 --- a/src/generators/shared/processCss.ts +++ b/src/generators/shared/processCss.ts @@ -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' ) { diff --git a/src/generators/shared/utils/walkHtml.ts b/src/generators/shared/utils/walkHtml.ts index 39568cd742..ad5d826e1a 100644 --- a/src/generators/shared/utils/walkHtml.ts +++ b/src/generators/shared/utils/walkHtml.ts @@ -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 ); }); } diff --git a/src/index.ts b/src/index.ts index 19c5b41930..b0fd1e7b30 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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; diff --git a/src/interfaces.ts b/src/interfaces.ts index 897c50b625..0ffe275064 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -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 } \ No newline at end of file diff --git a/src/validate/index.ts b/src/validate/index.ts index b09374fce1..891132d23b 100644 --- a/src/validate/index.ts +++ b/src/validate/index.ts @@ -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; transitions: Map; - 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` );