diff --git a/src/bin/ts-node.ts b/src/bin/ts-node.ts index c1f31b37f..0826e1760 100644 --- a/src/bin/ts-node.ts +++ b/src/bin/ts-node.ts @@ -7,7 +7,7 @@ import { readFileSync } from 'fs' import Module = require('module') import extend = require('xtend') import { runInThisContext } from 'vm' -import { register, createError, getInlineSourceMap, getDiagnostics } from '../typescript-node' +import { register } from '../typescript-node' var program = new Command('ts-node') var pkg = require('../../package.json') @@ -32,15 +32,13 @@ const eval = opts.eval const code: string = eval == null ? print : eval // Register returns environment options, helps creating a new language service. -const compiler = register(opts) +const compileInline = register(opts) // Defer creation of eval services. let files: { [filename: string]: { text: string, version: number } } let service: LanguageService if (typeof code === 'string') { - createService() - global.__filename = EVAL_FILENAME global.__dirname = cwd @@ -96,7 +94,6 @@ if (typeof code === 'string') { Module.runMain() } else { - createService() startRepl() } } @@ -104,67 +101,10 @@ if (typeof code === 'string') { /** * Evaluate the code snippet. */ -function _eval (text: string, filename: string) { - if (!files[filename]) { - files[filename] = { version: 0, text: '' } - } - - const file = files[filename] - - file.text = text - file.version++ - - const output = service.getEmitOutput(filename) - const diagnostics = getDiagnostics(service, filename, compiler.options) - - if (diagnostics.length) { - throw createError(diagnostics, compiler.ts) - } - - const result = output.outputFiles[1].text - const sourceMap = output.outputFiles[0].text - - const code = result.replace( - '//# sourceMappingURL=' + filename.replace(/\.ts$/, '.js.map'), - '//# sourceMappingURL=' + getInlineSourceMap(sourceMap, filename, text) - ) - - return runInThisContext(result, filename) -} - -/** - * Create an inline eval service on demand. - */ -function createService () { - const { ts, registry, config } = compiler - - // Initialize files object. - files = {} - - // Create language services for `eval`. - service = ts.createLanguageService( { - getScriptFileNames: () => { - return config.fileNames.concat(Object.keys(files)) - }, - getScriptVersion: (fileName) => files[fileName] && files[fileName].version.toString(), - getScriptSnapshot (fileName) { - const file = files[fileName] - - if (file) { - return ts.ScriptSnapshot.fromString(file.text) - } - - try { - return ts.ScriptSnapshot.fromString(readFileSync(fileName, 'utf-8')) - } catch (e) { - return - } - }, - getCurrentDirectory: () => cwd, - getScriptIsOpen: () => true, - getCompilationSettings: () => extend(config.options, { sourceMap: true }), - getDefaultLibFileName: (options) => ts.getDefaultLibFilePath(options) - }, registry) +function _eval (code: string, filename: string) { + // Adding `;` before the code is jank, but avoids issues with source map + // columns becoming negative. + return runInThisContext(compileInline(filename, ';' + code), filename) } /** diff --git a/src/typescript-node.ts b/src/typescript-node.ts index c30e70a55..f40955650 100644 --- a/src/typescript-node.ts +++ b/src/typescript-node.ts @@ -50,6 +50,8 @@ export function register (opts?: Options) { const maps: { [fileName: string]: string } = {} const files: { [fileName: string]: boolean } = {} + const versions: { [fileName: string]: number } = {} + const snapshots: { [fileName: string]: TS.IScriptSnapshot } = {} // Enable compiler overrides. options.compiler = options.compiler || 'typescript' @@ -71,8 +73,12 @@ export function register (opts?: Options) { const serviceHost: TS.LanguageServiceHost = { getScriptFileNames: () => config.fileNames.concat(Object.keys(files)), - getScriptVersion: (fileName) => 'node', + getScriptVersion: (fileName) => String(versions[fileName] || 'node'), getScriptSnapshot (fileName): TS.IScriptSnapshot { + if (snapshots[fileName]) { + return snapshots[fileName] + } + try { return ts.ScriptSnapshot.fromString(readFileSync(fileName, 'utf-8')) } catch (e) { @@ -89,19 +95,14 @@ export function register (opts?: Options) { const service = ts.createLanguageService(serviceHost, registry) const hasSourceMap = config.options.sourceMap - const retrieveSourceMap = sourceMapSupport.retrieveSourceMap - // Install source map support and read from cache. sourceMapSupport.install({ - handleUncaughtExceptions: false, - retrieveSourceMap (filename: string) { - var map = maps && maps[filename] + retrieveSourceMap (fileName: string) { + var map = maps && maps[fileName] if (map) { return { map, url: null } } - - return retrieveSourceMap(filename) } }) @@ -113,7 +114,10 @@ export function register (opts?: Options) { // Cache source maps where provided. if (hasSourceMap) { - maps[output.outputFiles[0].name] = output.outputFiles[0].text + const sourceText = service.getSourceFile(fileName).text + const sourceMapText = output.outputFiles[0].text + + maps[fileName] = getSourceMap(sourceMapText, fileName, sourceText) } // Log all diagnostics before exiting the program. @@ -135,12 +139,18 @@ export function register (opts?: Options) { require.extensions[extension] = loader }) - return { - registry, - ts, - config, - options + function compileInline (fileName: string, code: string) { + if (!versions[fileName]) { + versions[fileName] = 0 + } + + versions[fileName]++ + snapshots[fileName] = ts.ScriptSnapshot.fromString(code) + + return compile(fileName) } + + return compileInline } /** @@ -174,7 +184,7 @@ export function formatDiagnostic (diagnostic: TS.Diagnostic, ts: typeof TS, cwd: * Create a "TypeScript" error. */ export function createError (diagnostics: TS.Diagnostic[], ts: typeof TS): Error { - const message = ['Unable to compile TypeScript:'] + const message = ['Unable to compile TypeScript'] .concat(diagnostics.map((d) => formatDiagnostic(d, ts))) .join(EOL) @@ -184,14 +194,12 @@ export function createError (diagnostics: TS.Diagnostic[], ts: typeof TS): Error } /** - * Generate an base 64 inline source map. + * Sanitize the source map content. */ -export function getInlineSourceMap (map: string, fileName: string, code: string): string { +export function getSourceMap (map: string, fileName: string, code: string): string { var sourceMap = JSON.parse(map) sourceMap.file = fileName sourceMap.sources = [fileName] sourceMap.sourcesContent = [code] - delete sourceMap.sourceRoot - const inlineSourceMap = new Buffer(JSON.stringify(sourceMap)).toString('base64') - return 'data:application/json;base64,' + inlineSourceMap + return JSON.stringify(sourceMap) }