diff --git a/.travis.yml b/.travis.yml index 1cefdc63b..6a4fa06b6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,7 @@ script: env: - TYPESCRIPT=typescript@1.6 - TYPESCRIPT=typescript@1.7 + - TYPESCRIPT=typescript@1.8 - TYPESCRIPT=typescript@next node_js: diff --git a/package.json b/package.json index 61c7db1ff..c30fe9211 100644 --- a/package.json +++ b/package.json @@ -54,20 +54,18 @@ "istanbul": "^0.4.0", "mocha": "^2.1.0", "ntypescript": "^1.201507091536.1", - "pre-commit": "^1.0.10", "proxyquire": "^1.7.2", "tslint": "^3.2.1", - "typescript": "^1.6.2", + "typescript": "^1.8.7", "typings": "^0.7.7" }, "dependencies": { "arrify": "^1.0.0", "chalk": "^1.1.1", "diff": "^2.1.1", - "make-error": "^1.0.2", + "make-error": "^1.1.1", "minimist": "^1.2.0", "source-map-support": "^0.4.0", - "tsconfig": "^2.1.1", "xtend": "^4.0.0" } } diff --git a/src/bin/ts-node.ts b/src/bin/ts-node.ts index c01859345..5678089bb 100644 --- a/src/bin/ts-node.ts +++ b/src/bin/ts-node.ts @@ -5,6 +5,7 @@ import { start } from 'repl' import { inspect } from 'util' import Module = require('module') import minimist = require('minimist') +import chalk = require('chalk') import { diffLines } from 'diff' import { createScript } from 'vm' import { register, VERSION, getFile, getVersion, TSError } from '../ts-node' @@ -22,7 +23,7 @@ interface Argv { _: string[] } -const argv = minimist(process.argv.slice(2), { +const argv = minimist(process.argv.slice(2), { stopEarly: true, string: ['eval', 'print', 'compiler', 'project', 'ignoreWarnings'], boolean: ['help', 'version', 'disableWarnings', 'noProject'], @@ -71,8 +72,7 @@ const service = register({ ignoreWarnings: list(argv.ignoreWarnings), project: argv.project, disableWarnings: argv.disableWarnings, - noProject: argv.noProject, - isEval: isEval + noProject: argv.noProject }) // TypeScript files must always end with `.ts`. @@ -100,8 +100,7 @@ if (typeof code === 'string') { result = _eval(code, global) } catch (error) { if (error instanceof TSError) { - console.error(error.print()) - process.exit(1) + printAndExit(error) } throw error @@ -124,6 +123,32 @@ if (typeof code === 'string') { } } +const _emit = process.emit + +process.emit = function (type, error) { + // Print the error message when no other listeners are present. + if (type === 'uncaughtException' && error instanceof TSError && process.listeners(type).length === 0) { + return printAndExit(error) + } + + return _emit.apply(this, arguments) +} + +/** + * Stringify the `TSError` instance. + */ +function print (error: TSError) { + return chalk.bold(`${chalk.red('⨯')} Unable to compile TypeScript`) + `\n${error.diagnostics.join('\n')}` +} + +/** + * Print the error and exit. + */ +function printAndExit (error: TSError) { + console.error(print(error)) + process.exit(1) +} + /** * Evaluate the code snippet. */ @@ -204,9 +229,9 @@ function startRepl () { evalFile.input += identifier evalFile.version++ - const info = service.getTypeInfo(EVAL_PATH, evalFile.input.length) + const { name, comment } = service.getTypeInfo(EVAL_PATH, evalFile.input.length) - ;( repl).outputStream.write(`${info}\n`) + ;( repl).outputStream.write(`${chalk.bold(name)}\n${comment ? `${comment}\n` : ''}`) ;( repl).displayPrompt() evalFile.input = undo @@ -218,7 +243,7 @@ function startRepl () { * Eval code from the REPL. */ function replEval (code: string, context: any, filename: string, callback: (err?: Error, result?: any) => any) { - let err: Error + let err: any let result: any // TODO: Figure out how to handle completion here. @@ -231,7 +256,7 @@ function replEval (code: string, context: any, filename: string, callback: (err? result = _eval(code, context) } catch (error) { if (error instanceof TSError) { - err = error.print() + err = print(error) } else { err = error } diff --git a/src/ts-node.ts b/src/ts-node.ts index 91b2c16a5..c4b61f267 100644 --- a/src/ts-node.ts +++ b/src/ts-node.ts @@ -1,48 +1,40 @@ -import tsconfig = require('tsconfig') -import { relative, basename } from 'path' +import { relative, basename, resolve, dirname } from 'path' import { readFileSync, statSync } from 'fs' import { EOL } from 'os' import sourceMapSupport = require('source-map-support') import extend = require('xtend') import arrify = require('arrify') -import chalk = require('chalk') import { BaseError } from 'make-error' +import * as TS from 'typescript' /** * Common TypeScript interfaces between versions. */ export interface TSCommon { sys: any - service: any - ScriptSnapshot: { - fromString (value: string): any + ScriptSnapshot: typeof TS.ScriptSnapshot + displayPartsToString: typeof TS.displayPartsToString + createLanguageService: typeof TS.createLanguageService + getDefaultLibFilePath: typeof TS.getDefaultLibFilePath + getPreEmitDiagnostics: typeof TS.getPreEmitDiagnostics + flattenDiagnosticMessageText: typeof TS.flattenDiagnosticMessageText + + // TypeScript 1.5+, 1.7+ added `fileExists` parameter. + findConfigFile (path: string, fileExists?: (path: string) => boolean): string + + // TypeScript 1.5+, 1.7+ added `readFile` parameter. + readConfigFile (path: string, readFile?: (path: string) => string): { + config?: any + error?: TS.Diagnostic } - displayPartsToString (parts: any): string - createLanguageService (serviceHost: any): any - getDefaultLibFilePath (options: any): string - getPreEmitDiagnostics (program: any): any[] - flattenDiagnosticMessageText (diagnostic: any, newLine: string): string -} -/** - * The TypeScript 1.7+ interface. - */ -export interface TS17ish extends TSCommon { - parseJsonConfigFileContent (config: any, host: any, fileName: string): any -} + // TypeScript 1.7+. + parseJsonConfigFileContent? (json: any, host: any, basePath: string, existingOptions: any, configFileName: string): any -/** - * TypeScript 1.5+ interface. - */ -export interface TS15ish extends TSCommon { - parseConfigFile (config: any, host: any, fileName: string): any + // TypeScript 1.5+. + parseConfigFile? (json: any, host: any, basePath: string): any } -/** - * TypeScript compatible compilers. - */ -export type TSish = TS15ish | TS17ish - /** * Export the current version. */ @@ -61,7 +53,6 @@ export interface Options { noProject?: boolean project?: string ignoreWarnings?: Array - isEval?: boolean disableWarnings?: boolean getFile?: (fileName: string) => string getVersion?: (fileName: string) => string @@ -70,35 +61,44 @@ export interface Options { /** * Load TypeScript configuration. */ -function readConfig (options: Options, cwd: string, ts: TSish) { +function readConfig (options: Options, cwd: string, ts: TSCommon) { const { project, noProject } = options - const fileName = noProject ? undefined : tsconfig.resolveSync(project || cwd) + const fileName = noProject ? undefined : resolve(ts.findConfigFile(project || cwd, ts.sys.fileExists)) - const config = fileName ? tsconfig.readFileSync(fileName, { filterDefinitions: true }) : { - files: [], - compilerOptions: {} + const result = fileName ? ts.readConfigFile(fileName, ts.sys.readFile) : { + config: { + files: [], + compilerOptions: {} + } } - config.compilerOptions = extend( + if (result.error) { + throw new TSError([formatDiagnostic(result.error, ts)]) + } + + result.config.compilerOptions = extend( { target: 'es5', module: 'commonjs' }, - config.compilerOptions, + result.config.compilerOptions, { - rootDir: cwd, sourceMap: true, inlineSourceMap: false, inlineSources: false, - declaration: false + declaration: false, + noEmit: false } ) - if (typeof ( ts).parseConfigFile === 'function') { - return ( ts).parseConfigFile(config, ts.sys, fileName) + // Resolve before getting `dirname` to work around Microsoft/TypeScript#2965 + const basePath = dirname(resolve(fileName)) + + if (typeof ts.parseConfigFile === 'function') { + return ts.parseConfigFile(result.config, ts.sys, basePath) } - return ( ts).parseJsonConfigFileContent(config, ts.sys, fileName) + return ts.parseJsonConfigFileContent(result.config, ts.sys, basePath, null, fileName) } /** @@ -123,15 +123,12 @@ export function register (opts?: Options) { options.compiler = options.compiler || 'typescript' options.ignoreWarnings = arrify(options.ignoreWarnings).map(Number) - const ts: TSish = require(options.compiler) + const ts: typeof TS = require(options.compiler) const config = readConfig(options, cwd, ts) // Render the configuration errors and exit the script. if (!options.disableWarnings && config.errors.length) { - const diagnostics = config.errors.map((d: any) => formatDiagnostic(d, ts)) - - console.error(printDiagnostics(diagnostics)) - process.exit(1) + throw new TSError(config.errors.map((d: any) => formatDiagnostic(d, ts))) } // Add all files into the file hash. @@ -195,12 +192,7 @@ export function register (opts?: Options) { } if (diagnostics.length) { - if (options.isEval) { - throw new TSError(diagnostics) - } else { - console.error(printDiagnostics(diagnostics)) - process.exit(1) - } + throw new TSError(diagnostics) } const result = output.outputFiles[1].text @@ -236,7 +228,7 @@ export function register (opts?: Options) { const name = ts.displayPartsToString(info ? info.displayParts : []) const comment = ts.displayPartsToString(info ? info.documentation : []) - return chalk.bold(name) + (comment ? `${EOL}${comment}` : '') + return { name, comment } } // Attach the loader to each defined extension. @@ -268,7 +260,7 @@ export function getFile (fileName: string): string { /** * Get file diagnostics from a TypeScript language service. */ -function getDiagnostics (service: any, fileName: string, options: Options, ts: TSish) { +function getDiagnostics (service: any, fileName: string, options: Options, ts: TSCommon) { if (options.disableWarnings) { return [] } @@ -285,7 +277,7 @@ function getDiagnostics (service: any, fileName: string, options: Options, ts: T /** * Format a diagnostic object into a string. */ -function formatDiagnostic (diagnostic: any, ts: TSish, cwd: string = '.'): string { +function formatDiagnostic (diagnostic: any, ts: TSCommon, cwd: string = '.'): string { const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n') if (diagnostic.file) { @@ -298,21 +290,6 @@ function formatDiagnostic (diagnostic: any, ts: TSish, cwd: string = '.'): strin return `${message} (${diagnostic.code})` } -/** - * Format diagnostics into friendlier errors. - */ -export function printDiagnostics (diagnostics: string[]) { - const boundary = chalk.grey('----------------------------------') - - return [ - boundary, - chalk.red.bold('⨯ Unable to compile TypeScript'), - '', - diagnostics.join(EOL), - boundary - ].join(EOL) -} - /** * Sanitize the source map content. */ @@ -334,12 +311,8 @@ export class TSError extends BaseError { diagnostics: string[] constructor (diagnostics: string[]) { - super('Unable to compile TypeScript') + super(`⨯ Unable to compile TypeScript\n${diagnostics.join('\n')}`) this.diagnostics = diagnostics } - print () { - return printDiagnostics(this.diagnostics) - } - } diff --git a/tsconfig.json b/tsconfig.json index 8a812e484..daf42307b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,7 @@ "declaration": true, "noImplicitAny": true, "removeComments": true, - "moduleResolution": "classic", + "moduleResolution": "node", "sourceMap": true }, "files": [ diff --git a/typings.json b/typings.json index ced6dea9e..e6515b2a2 100644 --- a/typings.json +++ b/typings.json @@ -1,22 +1,20 @@ { "dependencies": { - "arrify": "github:typings/typed-arrify#32383d5fd3e6a8614abb6dba254a3aab07cd7424", - "chalk": "github:typings/typed-chalk#377de52aeac245226394c41fa6adcd31dbd17d51", - "diff": "github:typings/typed-diff#9b748f41b48c9ddcca5c2a135edd57df25d578cd", - "make-error": "github:typings/typed-make-error#5a653400e91cf7046512544b7ed2ebab702b0183", - "minimist": "github:typings/typed-minimist#00d73ec84bd64f30afff738aa2fc96c79c1ab1b1", - "proxyquire": "github:typings/typed-proxyquire#e94c99bbac8d350e28edd70471d9460a5534927b", - "source-map-support": "github:typings/typed-source-map-support#900ed4180a22285bce4bbabc0760427e71a59eca", - "tsconfig": "npm:tsconfig", - "typescript": "npm:typescript/lib/typescript.d.ts", - "xtend": "github:typings/typed-xtend#63cccadf3295b3c15561ee45617ac006edcca9e0" + "arrify": "registry:npm/arrify#1.0.0+20160211003958", + "chalk": "registry:npm/chalk#1.0.0+20160211003958", + "diff": "registry:npm/diff#2.0.0+20160211003958", + "make-error": "registry:npm/make-error#1.0.0+20160211003958", + "minimist": "registry:npm/minimist#1.0.0+20160229232932", + "proxyquire": "registry:npm/proxyquire#1.0.0+20160211003958", + "source-map-support": "registry:npm/source-map-support#0.3.0+20160211003958", + "xtend": "registry:npm/xtend#4.0.0+20160211003958" }, "ambientDependencies": { "node": "github:borisyankov/DefinitelyTyped/node/node.d.ts#b37afda34daa6186c3f143609555fcd6d70b249f", "node-extended": "file:custom_typings/node.d.ts" }, "ambientDevDependencies": { - "chai": "github:borisyankov/DefinitelyTyped/chai/chai.d.ts#b37afda34daa6186c3f143609555fcd6d70b249", - "mocha": "github:borisyankov/DefinitelyTyped/mocha/mocha.d.ts#b37afda34daa6186c3f143609555fcd6d70b249f" + "chai": "registry:dt/chai#3.4.0+20160216071402", + "mocha": "registry:dt/mocha#2.2.5+20151023103246" } -} \ No newline at end of file +}