Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: TypeStrong/ts-node
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v1.4.3
Choose a base ref
...
head repository: TypeStrong/ts-node
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v1.5.0
Choose a head ref
  • 3 commits
  • 5 files changed
  • 1 contributor

Commits on Oct 14, 2016

  1. Copy the full SHA
    7fba315 View commit details

Commits on Oct 15, 2016

  1. Copy the full SHA
    1553197 View commit details
  2. v1.5.0

    blakeembrey committed Oct 15, 2016
    Copy the full SHA
    df58bdc View commit details
Showing with 143 additions and 143 deletions.
  1. +1 −2 package.json
  2. +83 −94 src/_bin.ts
  3. +2 −1 src/index.spec.ts
  4. +56 −44 src/index.ts
  5. +1 −2 typings.json
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ts-node",
"version": "1.4.3",
"version": "1.5.0",
"preferGlobal": true,
"description": "TypeScript execution environment and REPL for node",
"main": "dist/index.js",
@@ -59,7 +59,6 @@
"dependencies": {
"arrify": "^1.0.0",
"chalk": "^1.1.1",
"diff": "^3.0.0",
"make-error": "^1.1.1",
"minimist": "^1.2.0",
"mkdirp": "^0.5.1",
177 changes: 83 additions & 94 deletions src/_bin.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { join, resolve } from 'path'
import { start } from 'repl'
import { join, resolve, basename } from 'path'
import { start, Recoverable } from 'repl'
import { inspect } from 'util'
import arrify = require('arrify')
import Module = require('module')
import minimist = require('minimist')
import chalk = require('chalk')
import { diffLines } from 'diff'
import { createScript } from 'vm'
import { register, VERSION, getFile, fileExists, TSError, parse } from './index'

@@ -137,25 +136,12 @@ Options:
process.exit(0)
}

/**
* Override `process.emit` for clearer compiler errors.
*/
const _emit = process.emit

process.emit = function (type, error): boolean {
// Print the error message when no other listeners are present.
if (type === 'uncaughtException' && error instanceof TSError && process.listeners(type).length === 0) {
printAndExit(error)
}

return _emit.apply(this, arguments)
}

const cwd = process.cwd()
const code = argv.eval == null ? argv.print : argv.eval
const isEvalScript = typeof argv.eval === 'string' || !!argv.print // Minimist struggles with empty strings.
const isEval = isEvalScript || stop === process.argv.length
const isPrinted = argv.print != null
const supportsScriptOptions = parseFloat(process.version.substr(1)) >= 1

// Register the TypeScript compiler instance.
const service = register({
@@ -173,12 +159,11 @@ const service = register({
fileExists: isEval ? fileExistsEval : fileExists
})

// TypeScript files must always end with `.ts`.
const EVAL_FILENAME = '[eval].ts'
const EVAL_PATH = join(cwd, EVAL_FILENAME)
// Increment the `eval` id to keep track of execution.
let evalId = 0

// Store eval contents for in-memory lookups.
const evalFile = { input: '', output: '', version: 0 }
// Note: TypeScript files must always end with `.ts`.
const EVAL_PATHS: { [path: string]: string } = {}

// Require specified modules before start-up.
for (const id of arrify(argv.require)) {
@@ -211,13 +196,14 @@ if (isEvalScript) {
* Evaluate a script.
*/
function evalAndExit (code: string, isPrinted: boolean) {
;(global as any).__filename = EVAL_FILENAME
;(global as any).__dirname = cwd
const filename = getEvalFileName(evalId)

const module = new Module((global as any).__filename)
module.filename = (global as any).__filename
module.paths = Module._nodeModulePaths((global as any).__dirname)
const module = new Module(filename)
module.filename = filename
module.paths = Module._nodeModulePaths(cwd)

;(global as any).__filename = filename
;(global as any).__dirname = cwd
;(global as any).exports = module.exports
;(global as any).module = module
;(global as any).require = module.require.bind(module)
@@ -228,7 +214,8 @@ function evalAndExit (code: string, isPrinted: boolean) {
result = _eval(code, global)
} catch (error) {
if (error instanceof TSError) {
printAndExit(error)
console.error(print(error))
process.exit(1)
}

throw error
@@ -245,60 +232,27 @@ function evalAndExit (code: string, isPrinted: boolean) {
* Stringify the `TSError` instance.
*/
function print (error: TSError) {
return chalk.bold(`${chalk.red('⨯')} Unable to compile TypeScript`) + `\n${error.diagnostics.join('\n')}`
}
const title = `${chalk.red('⨯')} Unable to compile TypeScript`

/**
* Print the error and exit.
*/
function printAndExit (error: TSError) {
console.error(print(error))
process.exit(1)
return `${chalk.bold(title)}\n${error.diagnostics.map(x => x.message).join('\n')}`
}

/**
* Evaluate the code snippet.
*/
function _eval (code: string, context: any) {
const undo = evalFile.input
const isCompletion = !/\n$/.test(code)

// Increment eval constants for the compiler to pick up changes.
evalFile.input += code
evalFile.version++

let output: string

// Undo on TypeScript compilation errors.
try {
output = service().compile(evalFile.input, EVAL_PATH)
} catch (error) {
evalFile.input = undo

throw error
}

// Use `diff` to check for new JavaScript to execute.
const changes = diffLines(evalFile.output, output)

// Revert the code if running in "completion" environment. Updated the output
// to diff against future executions when evaling code.
if (isCompletion) {
evalFile.input = undo
} else {
evalFile.output = output
}
function _eval (input: string, context: any) {
const isCompletion = !/\n$/.test(input)
const path = join(cwd, getEvalFileName(evalId++))
const { code, lineOffset } = getEvalContent(input)
const filename = basename(path)

let result: any
const output = service().compile(code, path, lineOffset)

// Iterate over the diff and evaluate `added` lines. The only removed lines
// should be the source map and lines that stay the same are ignored.
for (const change of changes) {
if (change.added) {
const script = createScript(change.value, EVAL_FILENAME)
const script = createScript(output, supportsScriptOptions ? { filename, lineOffset } : filename)
const result = script.runInNewContext(context)

result = script.runInNewContext(context)
}
if (!isCompletion) {
EVAL_PATHS[path] = code
}

return result
@@ -316,32 +270,27 @@ function startRepl () {
useGlobal: false
})

// Reset eval file information when repl is reset.
repl.on('reset', () => {
evalFile.input = ''
evalFile.output = ''
evalFile.version = 0
})

;(repl as any).defineCommand('type', {
repl.defineCommand('type', {
help: 'Check the type of a TypeScript identifier',
action: function (identifier: string) {
if (!identifier) {
;(repl as any).displayPrompt()
repl.displayPrompt()
return
}

const undo = evalFile.input
const path = join(cwd, getEvalFileName(evalId++))
const { code, lineOffset } = getEvalContent(identifier)

evalFile.input += identifier
evalFile.version++
// Cache the file for language services lookup.
EVAL_PATHS[path] = code

const { name, comment } = service().getTypeInfo(EVAL_PATH, evalFile.input.length)
const { name, comment } = service().getTypeInfo(path, code.length)

;(repl as any).outputStream.write(`${chalk.bold(name)}\n${comment ? `${comment}\n` : ''}`)
;(repl as any).displayPrompt()
// Delete the file from the cache after used for lookup.
delete EVAL_PATHS[path]

evalFile.input = undo
repl.outputStream.write(`${chalk.bold(name)}\n${comment ? `${comment}\n` : ''}`)
repl.displayPrompt()
}
})
}
@@ -363,7 +312,12 @@ function replEval (code: string, context: any, filename: string, callback: (err?
result = _eval(code, context)
} catch (error) {
if (error instanceof TSError) {
err = print(error)
// Support recoverable compilations using >= node 6.
if (typeof Recoverable === 'function' && isRecoverable(error)) {
err = new Recoverable(error)
} else {
err = print(error)
}
} else {
err = error
}
@@ -375,13 +329,48 @@ function replEval (code: string, context: any, filename: string, callback: (err?
/**
* Get the file text, checking for eval first.
*/
function getFileEval (fileName: string) {
return fileName === EVAL_PATH ? evalFile.input : getFile(fileName)
function getFileEval (path: string) {
return EVAL_PATHS.hasOwnProperty(path) ? EVAL_PATHS[path] : getFile(path)
}

/**
* Get whether the file exists.
*/
function fileExistsEval (fileName: string) {
return fileName === EVAL_PATH ? true : fileExists(fileName)
function fileExistsEval (path: string) {
return EVAL_PATHS.hasOwnProperty(path) || fileExists(path)
}

/**
* Create an file for evaluation.
*/
function getEvalContent (input: string) {
const refs = Object.keys(EVAL_PATHS).map(x => `/// <reference path="${x}" />\n`)

return {
lineOffset: -refs.length,
code: refs.join('') + input
}
}

/**
* Retrieve the eval filename.
*/
function getEvalFileName (index: number) {
return `[eval ${index}].ts`
}

const RECOVERY_CODES: number[] = [
1003, // "Identifier expected."
1005, // "')' expected."
1109, // "Expression expected."
1126, // "Unexpected end of text."
1160, // "Unterminated template literal."
1161 // "Unterminated regular expression literal."
]

/**
* Check if a function can recover gracefully.
*/
function isRecoverable (error: TSError) {
return error.diagnostics.every(x => RECOVERY_CODES.indexOf(x.code) > -1)
}
3 changes: 2 additions & 1 deletion src/index.spec.ts
Original file line number Diff line number Diff line change
@@ -78,7 +78,8 @@ describe('ts-node', function () {
it('should throw errors', function (done) {
exec(`${BIN_EXEC} -e "import * as m from './tests/module';console.log(m.example(123))"`, function (err) {
expect(err.message).to.match(new RegExp(
'\\[eval\\]\\.ts \\(1,59\\): Argument of type \'(?:number|123)\' ' +
// Node 0.10 can not override the `lineOffset` option.
'\\[eval [01]\\]\\.ts \\(1,59\\): Argument of type \'(?:number|123)\' ' +
'is not assignable to parameter of type \'string\'\\. \\(2345\\)'
))

Loading