diff --git a/src/generators/Generator.ts b/src/generators/Generator.ts index e20deb75bb..48491e410c 100644 --- a/src/generators/Generator.ts +++ b/src/generators/Generator.ts @@ -15,11 +15,12 @@ import clone from '../utils/clone'; import DomBlock from './dom/Block'; import SsrBlock from './server-side-rendering/Block'; import Stylesheet from '../css/Stylesheet'; -import { Node, Parsed, CompileOptions } from '../interfaces'; +import { Node, GenerateOptions, Parsed, CompileOptions } from '../interfaces'; const test = typeof global !== 'undefined' && global.__svelte_test; export default class Generator { + ast: Parsed; parsed: Parsed; source: string; name: string; @@ -78,9 +79,6 @@ export default class Generator { // styles this.stylesheet = stylesheet; - // TODO this is legacy — just to get the tests to pass during the transition - this.css = this.stylesheet.render(options.cssOutputFilename).css; - // allow compiler to deconflict user's `import { get } from 'whatever'` and // Svelte's builtin `import { get, ... } from 'svelte/shared.ts'`; this.importedNames = new Set(); @@ -263,7 +261,7 @@ export default class Generator { return (expression._dependencies = dependencies); } - generate(result, options: CompileOptions, { name, format }) { + generate(result: string, options: CompileOptions, { name, format }: GenerateOptions ) { if (this.imports.length) { const statements: string[] = []; diff --git a/src/generators/shared/utils/getGlobals.ts b/src/generators/shared/utils/getGlobals.ts index 1c00a08f60..8133757b4d 100644 --- a/src/generators/shared/utils/getGlobals.ts +++ b/src/generators/shared/utils/getGlobals.ts @@ -1,8 +1,7 @@ -import { Declaration, Options } from './getIntro'; +import { CompileOptions, Node } from '../../../interfaces'; -export type Globals = (id: string) => any; -export default function getGlobals(imports: Declaration[], options: Options) { +export default function getGlobals(imports: Node[], options: CompileOptions) { const { globals, onerror, onwarn } = options; const globalFn = getGlobalFn(globals); @@ -40,7 +39,7 @@ export default function getGlobals(imports: Declaration[], options: Options) { }); } -function getGlobalFn(globals: any): Globals { +function getGlobalFn(globals: any): (id: string) => string { if (typeof globals === 'function') return globals; if (typeof globals === 'object') { return id => globals[id]; diff --git a/src/generators/shared/utils/getIntro.ts b/src/generators/shared/utils/getIntro.ts index 7427f69a0b..6b043fcfec 100644 --- a/src/generators/shared/utils/getIntro.ts +++ b/src/generators/shared/utils/getIntro.ts @@ -1,29 +1,11 @@ import deindent from '../../../utils/deindent'; -import getGlobals, { Globals } from './getGlobals'; - -export type ModuleFormat = 'es' | 'amd' | 'cjs' | 'iife' | 'umd' | 'eval'; - -export interface Options { - name: string; - amd?: { - id?: string; - }; - globals: Globals | object; - onerror: (err: Error) => void; - onwarn: (obj: Error | { message: string }) => void; -} - -export interface Declaration { - name: string; - source: { - value: string; - }; -} +import getGlobals from './getGlobals'; +import { CompileOptions, ModuleFormat, Node } from '../../../interfaces'; export default function getIntro( format: ModuleFormat, - options: Options, - imports: Declaration[] + options: CompileOptions, + imports: Node[] ) { if (format === 'es') return ''; if (format === 'amd') return getAmdIntro(options, imports); @@ -35,7 +17,7 @@ export default function getIntro( throw new Error(`Not implemented: ${format}`); } -function getAmdIntro(options: Options, imports: Declaration[]) { +function getAmdIntro(options: CompileOptions, imports: Node[]) { const sourceString = imports.length ? `[ ${imports .map(declaration => `'${removeExtension(declaration.source.value)}'`) @@ -49,7 +31,7 @@ function getAmdIntro(options: Options, imports: Declaration[]) { : ''}${sourceString}function (${paramString(imports)}) { 'use strict';\n\n`; } -function getCjsIntro(options: Options, imports: Declaration[]) { +function getCjsIntro(options: CompileOptions, imports: Node[]) { const requireBlock = imports .map( declaration => @@ -64,7 +46,7 @@ function getCjsIntro(options: Options, imports: Declaration[]) { return `'use strict';\n\n`; } -function getIifeIntro(options: Options, imports: Declaration[]) { +function getIifeIntro(options: CompileOptions, imports: Node[]) { if (!options.name) { throw new Error(`Missing required 'name' option for IIFE export`); } @@ -74,7 +56,7 @@ function getIifeIntro(options: Options, imports: Declaration[]) { )}) { 'use strict';\n\n`; } -function getUmdIntro(options: Options, imports: Declaration[]) { +function getUmdIntro(options: CompileOptions, imports: Node[]) { if (!options.name) { throw new Error(`Missing required 'name' option for UMD export`); } @@ -101,11 +83,11 @@ function getUmdIntro(options: Options, imports: Declaration[]) { ); } -function getEvalIntro(options: Options, imports: Declaration[]) { +function getEvalIntro(options: CompileOptions, imports: Node[]) { return `(function (${paramString(imports)}) { 'use strict';\n\n`; } -function paramString(imports: Declaration[]) { +function paramString(imports: Node[]) { return imports.length ? ` ${imports.map(dep => dep.name).join(', ')} ` : ''; } diff --git a/src/generators/shared/utils/getOutro.ts b/src/generators/shared/utils/getOutro.ts index f9d8f186bc..682a3cc870 100644 --- a/src/generators/shared/utils/getOutro.ts +++ b/src/generators/shared/utils/getOutro.ts @@ -1,10 +1,11 @@ import getGlobals from './getGlobals'; +import { CompileOptions, Node } from '../../../interfaces'; export default function getOutro( format: string, name: string, - options, - imports + options: CompileOptions, + imports: Node[] ) { if (format === 'es') { return `export default ${name};`; diff --git a/src/generators/shared/utils/walkHtml.ts b/src/generators/shared/utils/walkHtml.ts index e12b36bbf2..b7ea97aa55 100644 --- a/src/generators/shared/utils/walkHtml.ts +++ b/src/generators/shared/utils/walkHtml.ts @@ -1,4 +1,4 @@ -import { Node } from '../../../interfaces'; +import { Node, Visitor } from '../../../interfaces'; export default function walkHtml(html: Node, visitors) { function visit(node: Node) { diff --git a/src/interfaces.ts b/src/interfaces.ts index 668c9816f8..4944bf8c46 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -39,6 +39,10 @@ export interface CompileOptions { name?: string; filename?: string; generate?: string; + globals?: ((id: string) => string) | object; + amd?: { + id?: string; + }; outputFilename?: string; cssOutputFilename?: string; @@ -51,3 +55,15 @@ export interface CompileOptions { onerror?: (error: Error) => void; onwarn?: (warning: Warning) => void; } + +export type ModuleFormat = 'es' | 'amd' | 'cjs' | 'iife' | 'umd' | 'eval'; + +export interface GenerateOptions { + name: string; + format: ModuleFormat; +} + +export interface Visitor { + enter: (node: Node) => void; + leave?: (node: Node) => void; +} \ No newline at end of file diff --git a/src/utils/clone.ts b/src/utils/clone.ts index d01b7aaa87..60b90c2208 100644 --- a/src/utils/clone.ts +++ b/src/utils/clone.ts @@ -1,7 +1,7 @@ -import { Node } from '../interfaces'; +import { Node, Parsed } from '../interfaces'; -export default function clone(node: Node) { - const cloned = {}; +export default function clone(node: Node|Parsed) { + const cloned: any = {}; for (const key in node) { const value = node[key]; diff --git a/src/utils/removeNode.ts b/src/utils/removeNode.ts index bce5984a98..6abea2f8c9 100644 --- a/src/utils/removeNode.ts +++ b/src/utils/removeNode.ts @@ -1,3 +1,4 @@ +import MagicString from 'magic-string'; import { Node } from '../interfaces'; const keys = { @@ -10,7 +11,7 @@ const offsets = { Program: [0, 0], }; -export function removeNode(code, parent: Node, node: Node) { +export function removeNode(code: MagicString, parent: Node, node: Node) { const key = keys[parent.type]; const offset = offsets[parent.type]; if (!key || !offset) throw new Error(`not implemented: ${parent.type}`); @@ -44,7 +45,7 @@ export function removeNode(code, parent: Node, node: Node) { return; } -export function removeObjectKey(code, node, key) { +export function removeObjectKey(code: MagicString, node: Node, key: string) { if (node.type !== 'ObjectExpression') return; let i = node.properties.length; diff --git a/src/validate/utils/FuzzySet.ts b/src/validate/utils/FuzzySet.ts index 1973d3ff83..f52242cf5d 100644 --- a/src/validate/utils/FuzzySet.ts +++ b/src/validate/utils/FuzzySet.ts @@ -1,115 +1,152 @@ // adapted from https://github.com/Glench/fuzzyset.js/blob/master/lib/fuzzyset.js // BSD Licensed -export default function FuzzySet( - arr, - useLevenshtein, - gramSizeLower, - gramSizeUpper -) { - // default options - arr = arr || []; - this.gramSizeLower = gramSizeLower || 2; - this.gramSizeUpper = gramSizeUpper || 3; - this.useLevenshtein = typeof useLevenshtein !== 'boolean' - ? true - : useLevenshtein; - - // define all the object functions and attributes - this.exactSet = {}; - this.matchDict = {}; - this.items = {}; - - // helper functions - function levenshtein(str1, str2) { - const current = []; - let prev; - let value; - - for (let i = 0; i <= str2.length; i++) { - for (let j = 0; j <= str1.length; j++) { - if (i && j) { - if (str1.charAt(j - 1) === str2.charAt(i - 1)) { - value = prev; - } else { - value = Math.min(current[j], current[j - 1], prev) + 1; - } +const GRAM_SIZE_LOWER = 2; +const GRAM_SIZE_UPPER = 3; + +// return an edit distance from 0 to 1 +function _distance(str1: string, str2: string) { + if (str1 === null && str2 === null) + throw 'Trying to compare two null values'; + if (str1 === null || str2 === null) return 0; + str1 = String(str1); + str2 = String(str2); + + const distance = levenshtein(str1, str2); + if (str1.length > str2.length) { + return 1 - distance / str1.length; + } else { + return 1 - distance / str2.length; + } +} + +// helper functions +function levenshtein(str1: string, str2: string) { + const current: number[] = []; + let prev; + let value; + + for (let i = 0; i <= str2.length; i++) { + for (let j = 0; j <= str1.length; j++) { + if (i && j) { + if (str1.charAt(j - 1) === str2.charAt(i - 1)) { + value = prev; } else { - value = i + j; + value = Math.min(current[j], current[j - 1], prev) + 1; } - - prev = current[j]; - current[j] = value; + } else { + value = i + j; } + + prev = current[j]; + current[j] = value; } + } + + return current.pop(); +} + +const _nonWordRe = /[^\w, ]+/; - return current.pop(); +function _iterateGrams(value: string, gramSize: number) { + gramSize = gramSize || 2; + const simplified = '-' + value.toLowerCase().replace(_nonWordRe, '') + '-'; + const lenDiff = gramSize - simplified.length; + const results = []; + + if (lenDiff > 0) { + for (let i = 0; i < lenDiff; ++i) { + value += '-'; + } + } + for (let i = 0; i < simplified.length - gramSize + 1; ++i) { + results.push(simplified.slice(i, i + gramSize)); } + return results; +} + +function _gramCounter(value: string, gramSize: number) { + // return an object where key=gram, value=number of occurrences + gramSize = gramSize || 2; + const result = {}; + const grams = _iterateGrams(value, gramSize); + let i = 0; - // return an edit distance from 0 to 1 - function _distance(str1, str2) { - if (str1 === null && str2 === null) - throw 'Trying to compare two null values'; - if (str1 === null || str2 === null) return 0; - str1 = String(str1); - str2 = String(str2); - - const distance = levenshtein(str1, str2); - if (str1.length > str2.length) { - return 1 - distance / str1.length; + for (i; i < grams.length; ++i) { + if (grams[i] in result) { + result[grams[i]] += 1; } else { - return 1 - distance / str2.length; + result[grams[i]] = 1; } } + return result; +} + +function sortDescending(a, b) { + return b[0] - a[0]; +} - const _nonWordRe = /[^\w, ]+/; +export default class FuzzySet { + exactSet: object; + matchDict: object; + items: object; - function _iterateGrams(value, gramSize) { - gramSize = gramSize || 2; - const simplified = '-' + value.toLowerCase().replace(_nonWordRe, '') + '-'; - const lenDiff = gramSize - simplified.length; - const results = []; + constructor(arr: string[]) { + // define all the object functions and attributes + this.exactSet = {}; + this.matchDict = {}; + this.items = {}; - if (lenDiff > 0) { - for (let i = 0; i < lenDiff; ++i) { - value += '-'; - } + // initialization + for (let i = GRAM_SIZE_LOWER; i < GRAM_SIZE_UPPER + 1; ++i) { + this.items[i] = []; } - for (let i = 0; i < simplified.length - gramSize + 1; ++i) { - results.push(simplified.slice(i, i + gramSize)); + // add all the items to the set + for (let i = 0; i < arr.length; ++i) { + this.add(arr[i]); } - return results; } - function _gramCounter(value, gramSize) { - // return an object where key=gram, value=number of occurrences - gramSize = gramSize || 2; - const result = {}; - const grams = _iterateGrams(value, gramSize); - let i = 0; + add(value: string) { + const normalizedValue = value.toLowerCase(); + if (normalizedValue in this.exactSet) { + return false; + } - for (i; i < grams.length; ++i) { - if (grams[i] in result) { - result[grams[i]] += 1; - } else { - result[grams[i]] = 1; - } + let i = GRAM_SIZE_LOWER; + for (i; i < GRAM_SIZE_UPPER + 1; ++i) { + this._add(value, i); } - return result; } - // the main functions - this.get = function(value, defaultValue) { - // check for value in set, returning defaultValue or null if none found - const result = this._get(value); - if (!result && typeof defaultValue !== 'undefined') { - return defaultValue; + _add(value: string, gramSize: number) { + const normalizedValue = value.toLowerCase(); + const items = this.items[gramSize] || []; + const index = items.length; + + items.push(0); + const gramCounts = _gramCounter(normalizedValue, gramSize); + let sumOfSquareGramCounts = 0; + let gram; + let gramCount; + + for (gram in gramCounts) { + gramCount = gramCounts[gram]; + sumOfSquareGramCounts += Math.pow(gramCount, 2); + if (gram in this.matchDict) { + this.matchDict[gram].push([index, gramCount]); + } else { + this.matchDict[gram] = [[index, gramCount]]; + } } - return result; + const vectorNormal = Math.sqrt(sumOfSquareGramCounts); + items[index] = [vectorNormal, normalizedValue]; + this.items[gramSize] = items; + this.exactSet[normalizedValue] = value; }; - this._get = function(value) { - const normalizedValue = this._normalizeStr(value); + get(value: string) { + const normalizedValue = value.toLowerCase(); const result = this.exactSet[normalizedValue]; if (result) { @@ -119,8 +156,8 @@ export default function FuzzySet( let results = []; // start with high gram size and if there are no results, go to lower gram sizes for ( - let gramSize = this.gramSizeUpper; - gramSize >= this.gramSizeLower; + let gramSize = GRAM_SIZE_UPPER; + gramSize >= GRAM_SIZE_LOWER; --gramSize ) { results = this.__get(value, gramSize); @@ -129,10 +166,10 @@ export default function FuzzySet( } } return null; - }; + } - this.__get = function(value, gramSize) { - const normalizedValue = this._normalizeStr(value); + __get(value: string, gramSize: number) { + const normalizedValue = value.toLowerCase(); const matches = {}; const gramCounts = _gramCounter(normalizedValue, gramSize); const items = this.items[gramSize]; @@ -159,17 +196,6 @@ export default function FuzzySet( } } - function isEmptyObject(obj) { - for (const prop in obj) { - if (obj.hasOwnProperty(prop)) return false; - } - return true; - } - - if (isEmptyObject(matches)) { - return null; - } - const vectorNormal = Math.sqrt(sumOfSquareGramCounts); let results = []; let matchScore; @@ -182,127 +208,28 @@ export default function FuzzySet( items[matchIndex][1], ]); } - function sortDescending(a, b) { - if (a[0] < b[0]) { - return 1; - } else if (a[0] > b[0]) { - return -1; - } else { - return 0; - } - } results.sort(sortDescending); - if (this.useLevenshtein) { - const newResults = []; - const endIndex = Math.min(50, results.length); - // truncate somewhat arbitrarily to 50 - for (let i = 0; i < endIndex; ++i) { - newResults.push([ - _distance(results[i][1], normalizedValue), - results[i][1], - ]); - } - results = newResults; - results.sort(sortDescending); + + let newResults = []; + const endIndex = Math.min(50, results.length); + // truncate somewhat arbitrarily to 50 + for (let i = 0; i < endIndex; ++i) { + newResults.push([ + _distance(results[i][1], normalizedValue), + results[i][1], + ]); } - const newResults = []; + results = newResults; + results.sort(sortDescending); + + newResults = []; for (let i = 0; i < results.length; ++i) { if (results[i][0] == results[0][0]) { newResults.push([results[i][0], this.exactSet[results[i][1]]]); } } - return newResults; - }; - - this.add = function(value) { - const normalizedValue = this._normalizeStr(value); - if (normalizedValue in this.exactSet) { - return false; - } - - let i = this.gramSizeLower; - for (i; i < this.gramSizeUpper + 1; ++i) { - this._add(value, i); - } - }; - - this._add = function(value, gramSize) { - const normalizedValue = this._normalizeStr(value); - const items = this.items[gramSize] || []; - const index = items.length; - - items.push(0); - const gramCounts = _gramCounter(normalizedValue, gramSize); - let sumOfSquareGramCounts = 0; - let gram; - let gramCount; - - for (gram in gramCounts) { - gramCount = gramCounts[gram]; - sumOfSquareGramCounts += Math.pow(gramCount, 2); - if (gram in this.matchDict) { - this.matchDict[gram].push([index, gramCount]); - } else { - this.matchDict[gram] = [[index, gramCount]]; - } - } - const vectorNormal = Math.sqrt(sumOfSquareGramCounts); - items[index] = [vectorNormal, normalizedValue]; - this.items[gramSize] = items; - this.exactSet[normalizedValue] = value; - }; - - this._normalizeStr = function(str) { - if (Object.prototype.toString.call(str) !== '[object String]') - throw 'Must use a string as argument to FuzzySet functions'; - return str.toLowerCase(); - }; - - // return length of items in set - this.length = function() { - let count = 0; - let prop; - for (prop in this.exactSet) { - if (this.exactSet.hasOwnProperty(prop)) { - count += 1; - } - } - return count; - }; - - // return is set is empty - this.isEmpty = function() { - for (const prop in this.exactSet) { - if (this.exactSet.hasOwnProperty(prop)) { - return false; - } - } - return true; - }; - - // return list of values loaded into set - this.values = function() { - const values = []; - - for (const prop in this.exactSet) { - if (this.exactSet.hasOwnProperty(prop)) { - values.push(this.exactSet[prop]); - } - } - return values; + return newResults; }; - - // initialization - let i = this.gramSizeLower; - for (i; i < this.gramSizeUpper + 1; ++i) { - this.items[i] = []; - } - // add all the items to the set - for (i = 0; i < arr.length; ++i) { - this.add(arr[i]); - } - - return this; -} +} \ No newline at end of file