Comparing version 0.0.4 to 0.0.5
379
bin/tsun.js
@@ -9,4 +9,2 @@ /// <reference path='./typings/node.d.ts' /> | ||
var ConsoleModule = require('console'); | ||
var Console = ConsoleModule.Console; | ||
var builtinLibs = require('repl')._builtinLibs; | ||
var ts = require('typescript'); | ||
@@ -16,2 +14,5 @@ var fs = require('fs'); | ||
var child_process = require('child_process'); | ||
var Console = ConsoleModule.Console; | ||
var builtinLibs = require('repl')._builtinLibs; | ||
// workaround for ts import | ||
colors.setTheme({ | ||
@@ -21,21 +22,38 @@ warn: 'red' | ||
var options = require('optimist') | ||
.usage('A simple ts REPL.\nUsage: $0') | ||
.usage('A TypeScript REPL.\nUsage: $0') | ||
.alias('h', 'help') | ||
.describe('h', 'Print this help message') | ||
.alias('f', 'force') | ||
.describe('f', 'Force tsi to evaluate code with ts errors.') | ||
.describe('f', 'Force tsun to evaluate code with ts errors.') | ||
.alias('v', 'verbose') | ||
.describe('v', 'Print compiled javascript before evaluating.'); | ||
.describe('v', 'Print compiled javascript before evaluating.') | ||
.alias('o', 'out') | ||
.describe('o', 'output directory relative to temporary') | ||
.alias('a', 'autoref') | ||
.describe('a', 'add reference of definition under ./typings directory') | ||
.describe('dere', "I-its's not like I'm an option so DON'T GET THE WRONG IDEA!"); | ||
var argv = options.argv; | ||
if (argv._.length) { | ||
if (argv._.length === 1) { | ||
runCode(); | ||
} | ||
if (argv.h) { | ||
options.showHelp(); | ||
process.exit(1); | ||
} | ||
function runCode() { | ||
// run code in temp path, and cleanup | ||
var temp = require('temp'); | ||
temp.track(); | ||
var tempPath = temp.mkdirSync('tsrun'); | ||
process.on('SIGINT', function () { return temp.cleanupSync(); }); | ||
process.on('SIGTERM', function () { return temp.cleanupSync(); }); | ||
var tempPath = temp.mkdirSync('tsrun'); | ||
var outDir = tempPath; | ||
if (argv.o) { | ||
outDir = path.join(tempPath, argv.o); | ||
} | ||
var compileError = compile(argv._, { | ||
outDir: outDir, | ||
noEmitOnError: true, | ||
target: 1 /* ES5 */, | ||
module: 1 /* CommonJS */, | ||
outDir: tempPath | ||
module: 1 /* CommonJS */ | ||
}); | ||
@@ -49,6 +67,6 @@ if (compileError) | ||
return arg; | ||
return path.join(tempPath, arg.replace(/ts$/, 'js')); | ||
return path.join(outDir, arg.replace(/ts$/, 'js')); | ||
}); | ||
child_process.execFileSync('node', newArgv, { | ||
stdio: [0, 1, 2] | ||
stdio: 'inherit' | ||
}); | ||
@@ -71,3 +89,2 @@ process.exit(); | ||
allDiagnostics.forEach(function (diagnostic) { | ||
console.log(diagnostic); | ||
var message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'); | ||
@@ -82,14 +99,32 @@ if (!diagnostic.file) | ||
} | ||
if (argv.h) { | ||
options.showHelp(); | ||
process.exit(1); | ||
} | ||
/** | ||
* interpreter start | ||
*/ | ||
var defaultPrompt = '> ', moreLinesPrompt = '..'; | ||
var defaultPrefix = ''; | ||
var context = createContext(); | ||
var verbose = argv.v; | ||
var libPath = path.resolve(__dirname, '../typings/node.d.ts'); | ||
var codes = "/// <reference path=\"" + libPath + "\" />"; | ||
function getDeclarationFiles() { | ||
var libPaths = [path.resolve(__dirname, '../typings/node.d.ts')]; | ||
if (argv.autoref) { | ||
try { | ||
var dirs = fs.readdirSync('typings'); | ||
for (var _i = 0; _i < dirs.length; _i++) { | ||
var dir = dirs[_i]; | ||
libPaths.push(path.join('typings', dir)); | ||
} | ||
} | ||
catch (e) { | ||
} | ||
} | ||
return libPaths; | ||
} | ||
function getInitialCommands() { | ||
var codes = getDeclarationFiles().map(function (dir) { return ("/// <reference path=\"" + dir + "\" />"); }); | ||
return codes.join('\n'); | ||
} | ||
var versionCounter = 0; | ||
var dummyFile = '__dummy__' + Math.random() + '.ts'; | ||
var dummyFile = 'TSUN.repl.generated.ts'; | ||
var codes = getInitialCommands(); | ||
var buffer = ''; | ||
var rl = createReadLine(); | ||
var serviceHost = { | ||
@@ -115,34 +150,36 @@ getCompilationSettings: function () { return ({ | ||
getCurrentDirectory: function () { return process.cwd(); }, | ||
getDefaultLibFileName: function (options) { return ts.getDefaultLibFilePath(options); } | ||
getDefaultLibFileName: function (options) { return path.join(__dirname, '../node_modules/typescript/bin/lib.core.es6.d.ts'); } | ||
}; | ||
var service = ts.createLanguageService(serviceHost, ts.createDocumentRegistry()); | ||
var rl = readline.createInterface({ | ||
input: process.stdin, | ||
output: process.stdout, | ||
completer: function (line) { | ||
// append new line to get completions, then revert new line | ||
versionCounter++; | ||
var originalCodes = codes; | ||
codes += '\n' + line; | ||
var completions = service.getCompletionsAtPosition(dummyFile, codes.length); | ||
if (!completions) { | ||
function createReadLine() { | ||
return readline.createInterface({ | ||
input: process.stdin, | ||
output: process.stdout, | ||
completer: function (line) { | ||
// append new line to get completions, then revert new line | ||
versionCounter++; | ||
var originalCodes = codes; | ||
codes += buffer + '\n' + line; | ||
var completions = service.getCompletionsAtPosition(dummyFile, codes.length); | ||
if (!completions) { | ||
codes = originalCodes; | ||
return [[], line]; | ||
} | ||
var prefix = /[A-Za-z_$]+$/.exec(line); | ||
var candidates = []; | ||
if (prefix) { | ||
var prefixStr = prefix[0]; | ||
candidates = completions.entries.filter(function (entry) { | ||
var name = entry.name; | ||
return name.substr(0, prefixStr.length) == prefixStr; | ||
}).map(function (entry) { return entry.name; }); | ||
} | ||
else { | ||
candidates = completions.entries.map(function (entry) { return entry.name; }); | ||
} | ||
codes = originalCodes; | ||
return [[], line]; | ||
return [candidates, prefix ? prefix[0] : line]; | ||
} | ||
var prefix = /[A-Za-z_$]+$/.exec(line); | ||
var candidates = []; | ||
if (prefix) { | ||
var prefixStr = prefix[0]; | ||
candidates = completions.entries.filter(function (entry) { | ||
var name = entry.name; | ||
return name.substr(0, prefixStr.length) == prefixStr; | ||
}).map(function (entry) { return entry.name; }); | ||
} | ||
else { | ||
candidates = completions.entries.map(function (entry) { return entry.name; }); | ||
} | ||
codes = originalCodes; | ||
return [candidates, prefix ? prefix[0] : line]; | ||
} | ||
}); | ||
}); | ||
} | ||
// Much of this function is from repl.REPLServer.createContext | ||
@@ -179,49 +216,213 @@ function createContext() { | ||
} | ||
function repl(prompt, prefix) { | ||
var getDeclarations = (function () { | ||
var declarations = {}; | ||
for (var _i = 0, _a = getDeclarationFiles(); _i < _a.length; _i++) { | ||
var file = _a[_i]; | ||
declarations[file] = service.getSourceFile(file).getNamedDeclarations().map(function (t) { return t.name; }); | ||
} | ||
return function (cached) { | ||
if (cached === void 0) { cached = false; } | ||
if (!cached) { | ||
declarations[dummyFile] = service.getSourceFile(dummyFile).getNamedDeclarations().map(function (t) { return t.name; }); | ||
} | ||
return declarations; | ||
}; | ||
})(); | ||
function getMemberInfo(member, file, parentDeclaration) { | ||
var pos = member.getEnd(); | ||
var quickInfo = service.getQuickInfoAtPosition(file, pos); | ||
if (quickInfo) | ||
return ts.displayPartsToString(quickInfo.displayParts); | ||
// DeclarationName includes Identifier which does not have name and will not go here | ||
var name = member.name && member.name.getText(); | ||
if (!name) | ||
return member.getText(); | ||
var declarations = getDeclarations(true)[file].filter(function (d) { return d.getText() === name; }); | ||
for (var _i = 0; _i < declarations.length; _i++) { | ||
var decl = declarations[_i]; | ||
var d = decl; | ||
if (parentDeclaration.parent.name.getText() == d.parent.parent.name.getText()) { | ||
var quickInfo_1 = service.getQuickInfoAtPosition(file, d.getEnd()); | ||
return ts.displayPartsToString(quickInfo_1.displayParts); | ||
} | ||
} | ||
return member.getText(); | ||
} | ||
function getTypeInfo(decl, file, detailed) { | ||
var pos = decl.getEnd(); | ||
var ret = [("declaration in: " + file)]; | ||
var quickInfo = service.getQuickInfoAtPosition(file, pos); | ||
ret.push(ts.displayPartsToString(quickInfo.displayParts)); | ||
if (!detailed) | ||
return ret; | ||
var parentName = ret[1].split(' ')[1]; | ||
var symbolType = quickInfo.displayParts[0].text; | ||
if (symbolType === 'interface' || symbolType === 'class') { | ||
var classLikeDeclaration = decl.parent; | ||
for (var _i = 0, _a = classLikeDeclaration.members; _i < _a.length; _i++) { | ||
var member = _a[_i]; | ||
var memberInfo = getMemberInfo(member, file, decl).split('\n').map(function (mInfo) { | ||
mInfo = mInfo.replace(new RegExp(parentName + '\\.', 'g'), ''); | ||
return ' ' + mInfo; | ||
}); | ||
ret.push(memberInfo.join('\n')); | ||
} | ||
} | ||
return ret; | ||
} | ||
function getType(name, detailed) { | ||
var declarations = getDeclarations(); | ||
for (var file in declarations) { | ||
var names = declarations[file]; | ||
var nameText = names.map(function (t) { return t.getText(); }); | ||
if (nameText.indexOf(name) >= 0) { | ||
var decl = names[nameText.indexOf(name)]; | ||
var infoString = getTypeInfo(decl, file, detailed); | ||
console.log(infoString.join('\n').cyan); | ||
return; | ||
} | ||
} | ||
console.log(("identifier " + name + " not found").yellow); | ||
} | ||
function printHelp() { | ||
console.log("\ntsun repl commands\n:type symbol print the type of an identifier\n:detail symbol print details identifier\n:clear clear all the code\n:print print code input so far\n:help print this manual\n:paste enter paste mode".blue); | ||
if (argv.dere) { | ||
console.log(':baka Who would like some pervert like you, baka~'.blue); | ||
} | ||
} | ||
function getDiagnostics() { | ||
var emit = service.getEmitOutput(dummyFile); | ||
var allDiagnostics = service.getCompilerOptionsDiagnostics() | ||
.concat(service.getSyntacticDiagnostics(dummyFile)) | ||
.concat(service.getSemanticDiagnostics(dummyFile)); | ||
allDiagnostics.forEach(function (diagnostic) { | ||
var message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'); | ||
console.warn(message.red.bold); | ||
}); | ||
return allDiagnostics; | ||
} | ||
function startEvaluate(code) { | ||
buffer = ''; | ||
var fallback = codes; | ||
codes += code; | ||
versionCounter++; | ||
var allDiagnostics = getDiagnostics(); | ||
if (allDiagnostics.length) { | ||
codes = fallback; | ||
if (defaultPrompt != '> ') { | ||
console.log(''); | ||
console.log(defaultPrompt, 'URUSAI URUSAI URUSAI'.magenta); | ||
console.log(''); | ||
} | ||
return repl(defaultPrompt); | ||
} | ||
var current = ts.transpile(code); | ||
// workaround | ||
if (code.trim().substr(0, 6) === 'import' && !current.trim()) { | ||
current = code.replace(/^\s*import/, 'var'); | ||
} | ||
if (verbose) { | ||
console.log(current.green); | ||
} | ||
try { | ||
var result = vm.runInContext(current, context); | ||
console.log(util.inspect(result, false, 2, true)); | ||
} | ||
catch (e) { | ||
console.log(e.stack); | ||
} | ||
} | ||
function waitForMoreLines(code, indentLevel) { | ||
if (/\n{2}$/.test(code)) { | ||
console.log('You typed two blank lines! start new command'.yellow); | ||
buffer = ''; | ||
return repl(defaultPrompt); | ||
} | ||
var nextPrompt = ''; | ||
for (var i = 0; i < indentLevel; i++) { | ||
nextPrompt += moreLinesPrompt; | ||
} | ||
buffer = code; | ||
repl(nextPrompt); | ||
} | ||
function replLoop(prompt, code) { | ||
code = buffer + '\n' + code; | ||
var openCurly = (code.match(/\{/g) || []).length; | ||
var closeCurly = (code.match(/\}/g) || []).length; | ||
var openParen = (code.match(/\(/g) || []).length; | ||
var closeParen = (code.match(/\)/g) || []).length; | ||
if (openCurly === closeCurly && openParen === closeParen) { | ||
startEvaluate(code); | ||
repl(defaultPrompt); | ||
} | ||
else { | ||
var indentLevel = openCurly - closeCurly + openParen - closeParen; | ||
waitForMoreLines(code, indentLevel); | ||
} | ||
} | ||
function addLine(line) { | ||
buffer += '\n' + line; | ||
} | ||
function enterPasteMode() { | ||
console.log('// entering paste mode, press ctrl-d to evaluate'.cyan); | ||
console.log(''); | ||
var oldPrompt = defaultPrompt; | ||
rl.setPrompt(''); | ||
rl.on('line', addLine); | ||
rl.once('close', function (d) { | ||
console.log('evaluating...'.cyan); | ||
rl.removeListener('line', addLine); | ||
startEvaluate(buffer); | ||
rl = createReadLine(); | ||
repl(defaultPrompt = oldPrompt); | ||
}); | ||
} | ||
// main loop | ||
function repl(prompt) { | ||
'use strict'; | ||
rl.question(prompt, function (code) { | ||
code = prefix + '\n' + code; | ||
var openCurly = (code.match(/\{/g) || []).length; | ||
var closeCurly = (code.match(/\}/g) || []).length; | ||
var openParen = (code.match(/\(/g) || []).length; | ||
var closeParen = (code.match(/\)/g) || []).length; | ||
if (openCurly === closeCurly && openParen === closeParen) { | ||
var fallback = codes; | ||
codes += code; | ||
versionCounter++; | ||
var current = ts.transpile(code); | ||
var emit = service.getEmitOutput(dummyFile); | ||
var allDiagnostics = service.getCompilerOptionsDiagnostics() | ||
.concat(service.getSyntacticDiagnostics(dummyFile)) | ||
.concat(service.getSemanticDiagnostics(dummyFile)); | ||
allDiagnostics.forEach(function (diagnostic) { | ||
var message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'); | ||
console.warn(message.red.bold); | ||
}); | ||
if (verbose) { | ||
console.debug(current); | ||
if (/^:(type|detail)/.test(code)) { | ||
var identifier = code.split(' ')[1]; | ||
if (!identifier) { | ||
console.log(':type|detail command need names!'.red); | ||
return repl(prompt); | ||
} | ||
if (allDiagnostics.length) { | ||
codes = fallback; | ||
return repl(defaultPrompt, defaultPrefix); | ||
} | ||
try { | ||
var result = vm.runInContext(current, context); | ||
console.log(util.inspect(result, false, 2, true)); | ||
} | ||
catch (e) { | ||
console.log(e.stack); | ||
} | ||
repl(defaultPrompt, defaultPrefix); | ||
getType(identifier, code.indexOf('detail') === 1); | ||
return repl(prompt); | ||
} | ||
else { | ||
var indentLevel = openCurly - closeCurly + openParen - closeParen; | ||
var nextPrompt = ''; | ||
for (var i = 0; i < indentLevel; i++) { | ||
nextPrompt += moreLinesPrompt; | ||
} | ||
repl(nextPrompt, code); | ||
if (/^:help/.test(code)) { | ||
printHelp(); | ||
return repl(prompt); | ||
} | ||
if (/^:clear/.test(code)) { | ||
codes = getInitialCommands(); | ||
buffer = ''; | ||
context = createContext(); | ||
return repl(defaultPrompt); | ||
} | ||
if (/^:print/.test(code)) { | ||
console.log(codes); | ||
return repl(prompt); | ||
} | ||
if (/^:paste/.test(code) && !buffer) { | ||
return enterPasteMode(); | ||
} | ||
if (argv.dere && /^:baka/.test(code)) { | ||
defaultPrompt = 'ξ(゚⊿゚)ξ> '; | ||
moreLinesPrompt = 'ζ(///*ζ) '; | ||
return repl(defaultPrompt); | ||
} | ||
replLoop(prompt, code); | ||
}); | ||
} | ||
repl(defaultPrompt, defaultPrefix); | ||
if (!argv.dere) { | ||
console.log('TSUN'.blue, ': TypeScript Upgraded Node'); | ||
console.log('type in TypeScript expression to evaluate'); | ||
console.log('type', ':help'.blue.bold, 'for commands in repl'); | ||
} | ||
else { | ||
console.log('TSUN'.magenta, " I'm- I'm not making this repl because I like you or anything!"); | ||
console.log("don'... don't type ", ':help'.magenta.bold, ', okay? Idiot!'); | ||
} | ||
console.log(''); | ||
repl(defaultPrompt); |
{ | ||
"name": "tsun", | ||
"version": "0.0.4", | ||
"version": "0.0.5", | ||
"description": "TSUN: a repl for TypeScript Upgraded Node", | ||
@@ -5,0 +5,0 @@ "bin": "./bin/tsun", |
# TSUN - TypeScript Upgraded Node | ||
TSUN, a TypeScript Upgraded Node, supports a REPL and interpreter for TypeScript. | ||
When invoked without ts file argument, TSUN works as a repl where you can type in expression. | ||
When you pass ts file to TSUN in command line argument, TSUN will automatically run it with invisible compilation. | ||
@@ -9,3 +11,8 @@ Feature: | ||
* Tab-completion support | ||
* Multiple Line Mode | ||
* Paste Mode | ||
* Definition Lookup | ||
* directly execute TypeScript application like `node` | ||
* [Vim-Worksheet](https://github.com/HerringtonDarkholme/vim-worksheet) support | ||
* And hidden feature for your exploration | ||
@@ -20,2 +27,4 @@ Install: | ||
* Use it as interpreter: `tsun path/to/app.ts` | ||
* Other repl command can be accessed by typing `:help` | ||
* Command Line options can be viewd by passing `-h` or `--help` option | ||
@@ -30,2 +39,19 @@ Note: | ||
ScreenShots: | ||
=== | ||
Tab-completion | ||
![Tab Completion](https://raw.githubusercontent.com/HerringtonDarkholme/typescript-repl/master/screenshot/completion.png) | ||
Multiple Line Editing, typing double blank lines will escape from Multiple line mode | ||
![Multiple Line Editing](https://raw.githubusercontent.com/HerringtonDarkholme/typescript-repl/master/screenshot/block.png) | ||
Paste Mode | ||
![Paste Mode](https://raw.githubusercontent.com/HerringtonDarkholme/typescript-repl/master/screenshot/paste.png) | ||
Definition Lookup | ||
![Definition Lookup](https://raw.githubusercontent.com/HerringtonDarkholme/typescript-repl/master/screenshot/type.png) | ||
And there is more for your exploration... | ||
TODO: | ||
@@ -35,2 +61,1 @@ === | ||
* Add tsun config | ||
* Add dere mode |
396
tsun.ts
@@ -5,10 +5,7 @@ /// <reference path='./typings/node.d.ts' /> | ||
var readline = require('readline') | ||
var util = require('util') | ||
var vm = require('vm') | ||
import readline = require('readline') | ||
import util = require('util') | ||
import vm = require('vm') | ||
import path = require('path') | ||
import ConsoleModule = require('console') | ||
var Console = ConsoleModule.Console | ||
var builtinLibs = require('repl')._builtinLibs | ||
import ts = require('typescript') | ||
@@ -20,2 +17,6 @@ import fs = require('fs') | ||
var Console = ConsoleModule.Console | ||
var builtinLibs = require('repl')._builtinLibs | ||
// workaround for ts import | ||
colors.setTheme({ | ||
@@ -26,23 +27,42 @@ warn: 'red' | ||
var options = require('optimist') | ||
.usage('A simple ts REPL.\nUsage: $0') | ||
.usage('A TypeScript REPL.\nUsage: $0') | ||
.alias('h', 'help') | ||
.describe('h', 'Print this help message') | ||
.alias('f', 'force') | ||
.describe('f', 'Force tsi to evaluate code with ts errors.') | ||
.describe('f', 'Force tsun to evaluate code with ts errors.') | ||
.alias('v', 'verbose') | ||
.describe('v', 'Print compiled javascript before evaluating.') | ||
.alias('o', 'out') | ||
.describe('o', 'output directory relative to temporary') | ||
.alias('a', 'autoref') | ||
.describe('a', 'add reference of definition under ./typings directory') | ||
.describe('dere', "I-its's not like I'm an option so DON'T GET THE WRONG IDEA!") | ||
var argv = options.argv | ||
if (argv._.length) { | ||
if (argv._.length === 1) { | ||
runCode() | ||
} | ||
if (argv.h) { | ||
options.showHelp() | ||
process.exit(1) | ||
} | ||
function runCode() { | ||
// run code in temp path, and cleanup | ||
var temp = require('temp') | ||
temp.track() | ||
let tempPath = temp.mkdirSync('tsrun') | ||
process.on('SIGINT', () => temp.cleanupSync()) | ||
process.on('SIGTERM', () => temp.cleanupSync()) | ||
let tempPath = temp.mkdirSync('tsrun') | ||
let outDir = tempPath | ||
if (argv.o) { | ||
outDir = path.join(tempPath, argv.o) | ||
} | ||
let compileError = compile(argv._, { | ||
outDir, | ||
noEmitOnError: true, | ||
target: ts.ScriptTarget.ES5, | ||
module: ts.ModuleKind.CommonJS, | ||
outDir: tempPath | ||
}) | ||
@@ -54,6 +74,6 @@ if (compileError) process.exit(compileError) | ||
if (!/\.ts$/.test(arg)) return arg | ||
return path.join(tempPath, arg.replace(/ts$/, 'js')) | ||
return path.join(outDir, arg.replace(/ts$/, 'js')) | ||
}) | ||
child_process.execFileSync('node', newArgv, { | ||
stdio: [0, 1, 2] | ||
stdio: 'inherit' | ||
}) | ||
@@ -71,2 +91,3 @@ process.exit() | ||
} | ||
function compile(fileNames: string[], options: ts.CompilerOptions): number { | ||
@@ -79,3 +100,2 @@ var program = ts.createProgram(fileNames, options); | ||
allDiagnostics.forEach(diagnostic => { | ||
console.log(diagnostic) | ||
var message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'); | ||
@@ -92,16 +112,35 @@ if (!diagnostic.file) return console.log(message) | ||
if (argv.h) { | ||
options.showHelp() | ||
process.exit(1) | ||
} | ||
/** | ||
* interpreter start | ||
*/ | ||
var defaultPrompt = '> ', moreLinesPrompt = '..'; | ||
var defaultPrefix = ''; | ||
var context = createContext(); | ||
var verbose = argv.v; | ||
var libPath = path.resolve(__dirname, '../typings/node.d.ts') | ||
var codes = `/// <reference path="${libPath}" />` | ||
function getDeclarationFiles() { | ||
var libPaths = [path.resolve(__dirname, '../typings/node.d.ts')] | ||
if (argv.autoref) { | ||
try { | ||
let dirs = fs.readdirSync('typings') | ||
for (let dir of dirs) { | ||
libPaths.push(path.join('typings', dir)) | ||
} | ||
} catch(e) { | ||
} | ||
} | ||
return libPaths | ||
} | ||
function getInitialCommands() { | ||
var codes = getDeclarationFiles().map(dir => `/// <reference path="${dir}" />`) | ||
return codes.join('\n') | ||
} | ||
var versionCounter = 0 | ||
var dummyFile = '__dummy__' + Math.random() + '.ts' | ||
var dummyFile = 'TSUN.repl.generated.ts' | ||
var codes = getInitialCommands() | ||
var buffer = '' | ||
var rl = createReadLine() | ||
var serviceHost: ts.LanguageServiceHost = { | ||
@@ -127,3 +166,3 @@ getCompilationSettings: () => ({ | ||
getCurrentDirectory: () => process.cwd(), | ||
getDefaultLibFileName: (options) => ts.getDefaultLibFilePath(options) | ||
getDefaultLibFileName: (options) => path.join(__dirname, '../node_modules/typescript/bin/lib.core.es6.d.ts') | ||
} | ||
@@ -133,30 +172,32 @@ | ||
var rl = readline.createInterface({ | ||
input: process.stdin, | ||
output: process.stdout, | ||
completer(line) { | ||
// append new line to get completions, then revert new line | ||
versionCounter++ | ||
let originalCodes = codes | ||
codes += '\n' + line | ||
let completions = service.getCompletionsAtPosition(dummyFile, codes.length) | ||
if (!completions) { | ||
function createReadLine() { | ||
return readline.createInterface({ | ||
input: process.stdin, | ||
output: process.stdout, | ||
completer(line) { | ||
// append new line to get completions, then revert new line | ||
versionCounter++ | ||
let originalCodes = codes | ||
codes += buffer + '\n' + line | ||
let completions = service.getCompletionsAtPosition(dummyFile, codes.length) | ||
if (!completions) { | ||
codes = originalCodes | ||
return [[], line] | ||
} | ||
let prefix = /[A-Za-z_$]+$/.exec(line) | ||
let candidates = [] | ||
if (prefix) { | ||
let prefixStr = prefix[0] | ||
candidates = completions.entries.filter((entry) => { | ||
let name = entry.name | ||
return name.substr(0, prefixStr.length) == prefixStr | ||
}).map(entry => entry.name) | ||
} else { | ||
candidates = completions.entries.map(entry => entry.name) | ||
} | ||
codes = originalCodes | ||
return [[], line] | ||
return [candidates, prefix ? prefix[0] : line] | ||
} | ||
let prefix = /[A-Za-z_$]+$/.exec(line) | ||
let candidates = [] | ||
if (prefix) { | ||
let prefixStr = prefix[0] | ||
candidates = completions.entries.filter((entry) => { | ||
let name = entry.name | ||
return name.substr(0, prefixStr.length) == prefixStr | ||
}).map(entry => entry.name) | ||
} else { | ||
candidates = completions.entries.map(entry => entry.name) | ||
} | ||
codes = originalCodes | ||
return [candidates, prefix ? prefix[0] : line] | ||
} | ||
}); | ||
}) | ||
} | ||
@@ -197,52 +238,227 @@ // Much of this function is from repl.REPLServer.createContext | ||
var getDeclarations = (function() { | ||
var declarations: {[a: string]: ts.DeclarationName[]} = {} | ||
for (let file of getDeclarationFiles()) { | ||
declarations[file] = service.getSourceFile(file).getNamedDeclarations().map(t => t.name) | ||
} | ||
return function(cached: boolean = false) { | ||
if (!cached) { | ||
declarations[dummyFile] = service.getSourceFile(dummyFile).getNamedDeclarations().map(t => t.name) | ||
} | ||
return declarations | ||
} | ||
})() | ||
function getMemberInfo(member, file, parentDeclaration): string { | ||
let pos = member.getEnd() | ||
let quickInfo = service.getQuickInfoAtPosition(file, pos) | ||
if (quickInfo) return ts.displayPartsToString(quickInfo.displayParts) | ||
// DeclarationName includes Identifier which does not have name and will not go here | ||
let name = member.name && member.name.getText() | ||
if (!name) return member.getText() | ||
let declarations = getDeclarations(true)[file].filter(d => d.getText() === name) | ||
for (let decl of declarations) { | ||
let d: any = decl | ||
if (parentDeclaration.parent.name.getText() == d.parent.parent.name.getText()) { | ||
let quickInfo = service.getQuickInfoAtPosition(file, d.getEnd()) | ||
return ts.displayPartsToString(quickInfo.displayParts) | ||
} | ||
} | ||
return member.getText() | ||
} | ||
function repl(prompt, prefix) { | ||
function getTypeInfo(decl: ts.Node, file: string, detailed: boolean): string[] { | ||
let pos = decl.getEnd() | ||
let ret = [`declaration in: ${file}`] | ||
let quickInfo = service.getQuickInfoAtPosition(file, pos) | ||
ret.push(ts.displayPartsToString(quickInfo.displayParts)) | ||
if (!detailed) return ret | ||
let parentName = ret[1].split(' ')[1] | ||
let symbolType = quickInfo.displayParts[0].text | ||
if ( symbolType === 'interface' || symbolType === 'class') { | ||
let classLikeDeclaration = <ts.ClassLikeDeclaration>decl.parent | ||
for (let member of classLikeDeclaration.members) { | ||
let memberInfo = getMemberInfo(member, file, decl).split('\n').map(mInfo => { | ||
mInfo = mInfo.replace(new RegExp(parentName + '\\.', 'g'), '') | ||
return ' ' + mInfo | ||
}) | ||
ret.push(memberInfo.join('\n')) | ||
} | ||
} | ||
return ret | ||
} | ||
function getType(name, detailed) { | ||
let declarations = getDeclarations() | ||
for (let file in declarations) { | ||
let names = declarations[file] | ||
let nameText = names.map(t => t.getText()) | ||
if (nameText.indexOf(name) >= 0) { | ||
let decl = names[nameText.indexOf(name)] | ||
let infoString = getTypeInfo(decl, file, detailed) | ||
console.log(infoString.join('\n').cyan) | ||
return | ||
} | ||
} | ||
console.log(`identifier ${name} not found`.yellow) | ||
} | ||
function printHelp() { | ||
console.log(` | ||
tsun repl commands | ||
:type symbol print the type of an identifier | ||
:detail symbol print details identifier | ||
:clear clear all the code | ||
:print print code input so far | ||
:help print this manual | ||
:paste enter paste mode`.blue) | ||
if (argv.dere) { | ||
console.log(':baka Who would like some pervert like you, baka~'.blue) | ||
} | ||
} | ||
function getDiagnostics() { | ||
let emit = service.getEmitOutput(dummyFile) | ||
let allDiagnostics = service.getCompilerOptionsDiagnostics() | ||
.concat(service.getSyntacticDiagnostics(dummyFile)) | ||
.concat(service.getSemanticDiagnostics(dummyFile)) | ||
allDiagnostics.forEach(diagnostic => { | ||
let message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n') | ||
console.warn(message.red.bold) | ||
}) | ||
return allDiagnostics | ||
} | ||
function startEvaluate(code) { | ||
buffer = '' | ||
let fallback = codes | ||
codes += code | ||
versionCounter++ | ||
let allDiagnostics = getDiagnostics() | ||
if (allDiagnostics.length) { | ||
codes = fallback | ||
if (defaultPrompt != '> ') { | ||
console.log('') | ||
console.log(defaultPrompt, 'URUSAI URUSAI URUSAI'.magenta) | ||
console.log('') | ||
} | ||
return repl(defaultPrompt); | ||
} | ||
let current = ts.transpile(code) | ||
// workaround | ||
if (code.trim().substr(0, 6) === 'import' && !current.trim()) { | ||
current = code.replace(/^\s*import/, 'var') | ||
} | ||
if (verbose) { | ||
console.log(current.green); | ||
} | ||
try { | ||
var result = vm.runInContext(current, context); | ||
console.log(util.inspect(result, false, 2, true)); | ||
} catch (e) { | ||
console.log(e.stack); | ||
} | ||
} | ||
function waitForMoreLines(code: string, indentLevel: number) { | ||
if (/\n{2}$/.test(code)) { | ||
console.log('You typed two blank lines! start new command'.yellow) | ||
buffer = '' | ||
return repl(defaultPrompt) | ||
} | ||
var nextPrompt = ''; | ||
for (var i = 0; i < indentLevel; i++) { | ||
nextPrompt += moreLinesPrompt; | ||
} | ||
buffer = code | ||
repl(nextPrompt); | ||
} | ||
function replLoop(prompt, code) { | ||
code = buffer + '\n' + code; | ||
var openCurly = (code.match(/\{/g) || []).length; | ||
var closeCurly = (code.match(/\}/g) || []).length; | ||
var openParen = (code.match(/\(/g) || []).length; | ||
var closeParen = (code.match(/\)/g) || []).length; | ||
if (openCurly === closeCurly && openParen === closeParen) { | ||
startEvaluate(code) | ||
repl(defaultPrompt) | ||
} else { | ||
let indentLevel = openCurly - closeCurly + openParen - closeParen; | ||
waitForMoreLines(code, indentLevel) | ||
} | ||
} | ||
function addLine(line) { | ||
buffer += '\n' + line | ||
} | ||
function enterPasteMode() { | ||
console.log('// entering paste mode, press ctrl-d to evaluate'.cyan) | ||
console.log('') | ||
let oldPrompt = defaultPrompt | ||
rl.setPrompt('') | ||
rl.on('line', addLine) | ||
rl.once('close', (d) => { | ||
console.log('evaluating...'.cyan) | ||
rl.removeListener('line', addLine) | ||
startEvaluate(buffer) | ||
rl = createReadLine() | ||
repl(defaultPrompt = oldPrompt) | ||
}) | ||
} | ||
// main loop | ||
function repl(prompt) { | ||
'use strict'; | ||
rl.question(prompt, function (code) { | ||
code = prefix + '\n' + code; | ||
var openCurly = (code.match(/\{/g) || []).length; | ||
var closeCurly = (code.match(/\}/g) || []).length; | ||
var openParen = (code.match(/\(/g) || []).length; | ||
var closeParen = (code.match(/\)/g) || []).length; | ||
if (openCurly === closeCurly && openParen === closeParen) { | ||
let fallback = codes | ||
codes += code | ||
versionCounter++ | ||
let current = ts.transpile(code) | ||
let emit = service.getEmitOutput(dummyFile) | ||
let allDiagnostics = service.getCompilerOptionsDiagnostics() | ||
.concat(service.getSyntacticDiagnostics(dummyFile)) | ||
.concat(service.getSemanticDiagnostics(dummyFile)) | ||
allDiagnostics.forEach(diagnostic => { | ||
let message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n') | ||
console.warn(message.red.bold); | ||
}) | ||
if (verbose) { | ||
console.debug(current); | ||
if (/^:(type|detail)/.test(code)) { | ||
let identifier = code.split(' ')[1] | ||
if (!identifier) { | ||
console.log(':type|detail command need names!'.red) | ||
return repl(prompt) | ||
} | ||
if (allDiagnostics.length) { | ||
codes = fallback | ||
return repl(defaultPrompt, defaultPrefix); | ||
} | ||
try { | ||
var result = vm.runInContext(current, context); | ||
console.log(util.inspect(result, false, 2, true)); | ||
} catch (e) { | ||
console.log(e.stack); | ||
} | ||
repl(defaultPrompt, defaultPrefix); | ||
} else { | ||
var indentLevel = openCurly - closeCurly + openParen - closeParen; | ||
var nextPrompt = ''; | ||
for (var i = 0; i < indentLevel; i++) { | ||
nextPrompt += moreLinesPrompt; | ||
} | ||
repl(nextPrompt, code); | ||
getType(identifier, code.indexOf('detail') === 1) | ||
return repl(prompt) | ||
} | ||
if (/^:help/.test(code)) { | ||
printHelp() | ||
return repl(prompt) | ||
} | ||
if (/^:clear/.test(code)) { | ||
codes = getInitialCommands() | ||
buffer = '' | ||
context = createContext() | ||
return repl(defaultPrompt) | ||
} | ||
if (/^:print/.test(code)) { | ||
console.log(codes) | ||
return repl(prompt) | ||
} | ||
if (/^:paste/.test(code) && !buffer) { | ||
return enterPasteMode() | ||
} | ||
if (argv.dere && /^:baka/.test(code)) { | ||
defaultPrompt = 'ξ(゚⊿゚)ξ> ' | ||
moreLinesPrompt = 'ζ(///*ζ) '; | ||
return repl(defaultPrompt) | ||
} | ||
replLoop(prompt, code) | ||
}); | ||
} | ||
repl(defaultPrompt, defaultPrefix); | ||
if (!argv.dere) { | ||
console.log('TSUN'.blue, ': TypeScript Upgraded Node') | ||
console.log('type in TypeScript expression to evaluate') | ||
console.log('type', ':help'.blue.bold, 'for commands in repl') | ||
} else { | ||
console.log('TSUN'.magenta, " I'm- I'm not making this repl because I like you or anything!") | ||
console.log("don'... don't type ", ':help'.magenta.bold, ', okay? Idiot!') | ||
} | ||
console.log('') | ||
repl(defaultPrompt); |
@@ -622,3 +622,3 @@ // Type definitions for Node.js v0.12.0 | ||
export interface ReadLine extends events.EventEmitter { | ||
setPrompt(prompt: string, length: number): void; | ||
setPrompt(prompt: string, length?: number): void; | ||
prompt(preserveCursor?: boolean): void; | ||
@@ -1462,1 +1462,30 @@ question(query: string, callback: Function): void; | ||
} | ||
interface Console { | ||
info(message?: any, ...optionalParams: any[]): void; | ||
warn(message?: any, ...optionalParams: any[]): void; | ||
error(message?: any, ...optionalParams: any[]): void; | ||
log(message?: any, ...optionalParams: any[]): void; | ||
profile(reportName?: string): void; | ||
assert(test?: boolean, message?: string, ...optionalParams: any[]): void; | ||
msIsIndependentlyComposed(element: Element): boolean; | ||
clear(): void; | ||
dir(value?: any, ...optionalParams: any[]): void; | ||
profileEnd(): void; | ||
count(countTitle?: string): void; | ||
groupEnd(): void; | ||
time(timerName?: string): void; | ||
timeEnd(timerName?: string): void; | ||
trace(): void; | ||
group(groupTitle?: string): void; | ||
dirxml(value: any): void; | ||
debug(message?: string, ...optionalParams: any[]): void; | ||
groupCollapsed(groupTitle?: string): void; | ||
select(element: Element): void; | ||
} | ||
declare var Console: { | ||
prototype: Console; | ||
new(): Console; | ||
} | ||
declare var console: Console; |
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
341512
13
3972
58