1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-22 07:14:47 -05:00

Import ts file from prototype without change

From commit 559453cf6c
Excluding v8worker.d.ts, main.ts, and deno.d.ts.

Updates tslint.json to be original settings.
This commit is contained in:
Ryan Dahl 2018-07-06 11:20:35 -04:00
parent 21e1425656
commit fe404dfce9
16 changed files with 1374 additions and 73 deletions

124
js/console.ts Normal file
View file

@ -0,0 +1,124 @@
const print = V8Worker2.print;
// tslint:disable-next-line:no-any
type ConsoleContext = Set<any>;
// tslint:disable-next-line:no-any
function getClassInstanceName(instance: any): string {
if (typeof instance !== "object") {
return "";
}
if (instance && instance.__proto__ && instance.__proto__.constructor) {
return instance.__proto__.constructor.name; // could be "Object" or "Array"
}
return "";
}
// tslint:disable-next-line:no-any
function stringify(ctx: ConsoleContext, value: any): string {
switch (typeof value) {
case "string":
return value;
case "number":
case "boolean":
case "undefined":
case "symbol":
return String(value);
case "function":
if (value.name && value.name !== "anonymous") {
// from MDN spec
return `[Function: ${value.name}]`;
}
return "[Function]";
case "object":
if (value === null) {
return "null";
}
if (ctx.has(value)) {
return "[Circular]";
}
ctx.add(value);
const entries: string[] = [];
if (Array.isArray(value)) {
for (const el of value) {
entries.push(stringify(ctx, el));
}
ctx.delete(value);
if (entries.length === 0) {
return "[]";
}
return `[ ${entries.join(", ")} ]`;
} else {
let baseString = "";
const className = getClassInstanceName(value);
let shouldShowClassName = false;
if (className && className !== "Object" && className !== "anonymous") {
shouldShowClassName = true;
}
for (const key of Object.keys(value)) {
entries.push(`${key}: ${stringify(ctx, value[key])}`);
}
ctx.delete(value);
if (entries.length === 0) {
baseString = "{}";
} else {
baseString = `{ ${entries.join(", ")} }`;
}
if (shouldShowClassName) {
baseString = `${className} ${baseString}`;
}
return baseString;
}
default:
return "[Not Implemented]";
}
}
// tslint:disable-next-line:no-any
function stringifyArgs(args: any[]): string {
const out: string[] = [];
for (const a of args) {
if (typeof a === "string") {
out.push(a);
} else {
// tslint:disable-next-line:no-any
out.push(stringify(new Set<any>(), a));
}
}
return out.join(" ");
}
export class Console {
// tslint:disable-next-line:no-any
log(...args: any[]): void {
print(stringifyArgs(args));
}
debug = this.log;
info = this.log;
// tslint:disable-next-line:no-any
warn(...args: any[]): void {
print(`ERROR: ${stringifyArgs(args)}`);
}
error = this.warn;
// tslint:disable-next-line:no-any
assert(condition: boolean, ...args: any[]): void {
if (!condition) {
throw new Error(`Assertion failed: ${stringifyArgs(args)}`);
}
}
}

6
js/deno.ts Normal file
View file

@ -0,0 +1,6 @@
// Copyright 2018 Ryan Dahl <ry@tinyclouds.org>
// All rights reserved. MIT License.
// Public deno module.
// TODO get rid of deno.d.ts
export { pub, sub } from "./dispatch";
export { readFileSync, writeFileSync } from "./os";

73
js/dispatch.ts Normal file
View file

@ -0,0 +1,73 @@
// Copyright 2018 Ryan Dahl <ry@tinyclouds.org>
// All rights reserved. MIT License.
import { typedArrayToArrayBuffer } from "./util";
import { _global } from "./globals";
import { deno as pb } from "./msg.pb";
export type MessageCallback = (msg: Uint8Array) => void;
//type MessageStructCallback = (msg: pb.IMsg) => void;
const send = V8Worker2.send;
const channels = new Map<string, MessageCallback[]>();
export function sub(channel: string, cb: MessageCallback): void {
let subscribers = channels.get(channel);
if (!subscribers) {
subscribers = [];
channels.set(channel, subscribers);
}
subscribers.push(cb);
}
/*
export function subMsg(channel: string, cb: MessageStructCallback): void {
sub(channel, (payload: Uint8Array) => {
const msg = pb.Msg.decode(payload);
if (msg.error != null) {
f.onError(new Error(msg.error));
} else {
cb(msg);
}
});
}
*/
export function pub(channel: string, payload: Uint8Array): null | ArrayBuffer {
const msg = pb.BaseMsg.fromObject({ channel, payload });
const ui8 = pb.BaseMsg.encode(msg).finish();
const ab = typedArrayToArrayBuffer(ui8);
return send(ab);
}
// Internal version of "pub".
// TODO add internal version of "sub"
export function pubInternal(channel: string, obj: pb.IMsg): null | pb.Msg {
const msg = pb.Msg.fromObject(obj);
const ui8 = pb.Msg.encode(msg).finish();
const resBuf = pub(channel, ui8);
if (resBuf != null && resBuf.byteLength > 0) {
const res = pb.Msg.decode(new Uint8Array(resBuf));
if (res != null && res.error != null && res.error.length > 0) {
throw Error(res.error);
}
return res;
} else {
return null;
}
}
V8Worker2.recv((ab: ArrayBuffer) => {
const msg = pb.BaseMsg.decode(new Uint8Array(ab));
const subscribers = channels.get(msg.channel);
if (subscribers == null) {
throw Error(`No subscribers for channel "${msg.channel}".`);
}
for (const subscriber of subscribers) {
subscriber(msg.payload);
}
});
// Delete the V8Worker2 from the global object, so that no one else can receive
// messages.
_global["V8Worker2"] = null;

146
js/fetch.ts Normal file
View file

@ -0,0 +1,146 @@
// Copyright 2018 Ryan Dahl <ry@tinyclouds.org>
// All rights reserved. MIT License.
import {
assert,
log,
createResolvable,
Resolvable,
typedArrayToArrayBuffer
} from "./util";
import { pubInternal, sub } from "./dispatch";
import { deno as pb } from "./msg.pb";
export function initFetch() {
sub("fetch", (payload: Uint8Array) => {
const msg = pb.Msg.decode(payload);
assert(msg.command === pb.Msg.Command.FETCH_RES);
const id = msg.fetchResId;
const f = fetchRequests.get(id);
assert(f != null, `Couldn't find FetchRequest id ${id}`);
f.onMsg(msg);
});
}
const fetchRequests = new Map<number, FetchRequest>();
class FetchResponse implements Response {
readonly url: string;
body: null;
bodyUsed = false; // TODO
status: number;
statusText = "FIXME"; // TODO
readonly type = "basic"; // TODO
redirected = false; // TODO
headers: null; // TODO
//private bodyChunks: Uint8Array[] = [];
private first = true;
constructor(readonly req: FetchRequest) {
this.url = req.url;
}
bodyWaiter: Resolvable<ArrayBuffer>;
arrayBuffer(): Promise<ArrayBuffer> {
this.bodyWaiter = createResolvable();
return this.bodyWaiter;
}
blob(): Promise<Blob> {
throw Error("not implemented");
}
formData(): Promise<FormData> {
throw Error("not implemented");
}
async json(): Promise<object> {
const text = await this.text();
return JSON.parse(text);
}
async text(): Promise<string> {
const ab = await this.arrayBuffer();
const decoder = new TextDecoder("utf-8");
return decoder.decode(ab);
}
get ok(): boolean {
return 200 <= this.status && this.status < 300;
}
clone(): Response {
throw Error("not implemented");
}
onHeader: (res: Response) => void;
onError: (error: Error) => void;
onMsg(msg: pb.Msg) {
if (msg.error !== null && msg.error !== "") {
//throw new Error(msg.error)
this.onError(new Error(msg.error));
return;
}
if (this.first) {
this.first = false;
this.status = msg.fetchResStatus;
this.onHeader(this);
} else {
// Body message. Assuming it all comes in one message now.
const ab = typedArrayToArrayBuffer(msg.fetchResBody);
this.bodyWaiter.resolve(ab);
}
}
}
let nextFetchId = 0;
//TODO implements Request
class FetchRequest {
private readonly id: number;
response: FetchResponse;
constructor(readonly url: string) {
this.id = nextFetchId++;
fetchRequests.set(this.id, this);
this.response = new FetchResponse(this);
}
onMsg(msg: pb.Msg) {
this.response.onMsg(msg);
}
destroy() {
fetchRequests.delete(this.id);
}
start() {
log("dispatch FETCH_REQ", this.id, this.url);
const res = pubInternal("fetch", {
command: pb.Msg.Command.FETCH_REQ,
fetchReqId: this.id,
fetchReqUrl: this.url
});
assert(res == null);
}
}
export function fetch(
input?: Request | string,
init?: RequestInit
): Promise<Response> {
const fetchReq = new FetchRequest(input as string);
const response = fetchReq.response;
return new Promise((resolve, reject) => {
// tslint:disable-next-line:no-any
response.onHeader = (response: any) => {
log("onHeader");
resolve(response);
};
response.onError = (error: Error) => {
log("onError", error);
reject(error);
};
fetchReq.start();
});
}

32
js/globals.ts Normal file
View file

@ -0,0 +1,32 @@
// Copyright 2018 Ryan Dahl <ry@tinyclouds.org>
// All rights reserved. MIT License.
import * as timer from "./timers";
// If you use the eval function indirectly, by invoking it via a reference
// other than eval, as of ECMAScript 5 it works in the global scope rather than
// the local scope. This means, for instance, that function declarations create
// global functions, and that the code being evaluated doesn't have access to
// local variables within the scope where it's being called.
export const globalEval = eval;
// A reference to the global object.
// TODO The underscore is because it's conflicting with @types/node.
export const _global = globalEval("this");
_global["window"] = _global; // Create a window object.
import "./url";
_global["setTimeout"] = timer.setTimeout;
_global["setInterval"] = timer.setInterval;
_global["clearTimeout"] = timer.clearTimer;
_global["clearInterval"] = timer.clearTimer;
import { Console } from "./console";
_global["console"] = new Console();
import { fetch } from "./fetch";
_global["fetch"] = fetch;
import { TextEncoder, TextDecoder } from "text-encoding";
_global["TextEncoder"] = TextEncoder;
_global["TextDecoder"] = TextDecoder;

View file

@ -25,9 +25,9 @@ window["denoMain"] = () => {
const argv: string[] = [];
for (let i = 0; i < msg.startArgvLength(); i++) {
const arg = msg.startArgv(i);
deno.print(`argv[${i}] ${arg}`);
argv.push(msg.startArgv(i));
}
deno.print(`argv ${argv}`);
};
function typedArrayToArrayBuffer(ta: Uint8Array): ArrayBuffer {

65
js/os.ts Normal file
View file

@ -0,0 +1,65 @@
// Copyright 2018 Ryan Dahl <ry@tinyclouds.org>
// All rights reserved. MIT License.
import { ModuleInfo } from "./types";
import { pubInternal } from "./dispatch";
import { deno as pb } from "./msg.pb";
import { assert } from "./util";
export function exit(exitCode = 0): void {
pubInternal("os", {
command: pb.Msg.Command.EXIT,
exitCode
});
}
export function codeFetch(
moduleSpecifier: string,
containingFile: string
): ModuleInfo {
const res = pubInternal("os", {
command: pb.Msg.Command.CODE_FETCH,
codeFetchModuleSpecifier: moduleSpecifier,
codeFetchContainingFile: containingFile
});
assert(res.command === pb.Msg.Command.CODE_FETCH_RES);
return {
moduleName: res.codeFetchResModuleName,
filename: res.codeFetchResFilename,
sourceCode: res.codeFetchResSourceCode,
outputCode: res.codeFetchResOutputCode
};
}
export function codeCache(
filename: string,
sourceCode: string,
outputCode: string
): void {
pubInternal("os", {
command: pb.Msg.Command.CODE_CACHE,
codeCacheFilename: filename,
codeCacheSourceCode: sourceCode,
codeCacheOutputCode: outputCode
});
}
export function readFileSync(filename: string): Uint8Array {
const res = pubInternal("os", {
command: pb.Msg.Command.READ_FILE_SYNC,
readFileSyncFilename: filename
});
return res.readFileSyncData;
}
export function writeFileSync(
filename: string,
data: Uint8Array,
perm: number
): void {
pubInternal("os", {
command: pb.Msg.Command.WRITE_FILE_SYNC,
writeFileSyncFilename: filename,
writeFileSyncData: data,
writeFileSyncPerm: perm
});
}

339
js/runtime.ts Normal file
View file

@ -0,0 +1,339 @@
// Copyright 2018 Ryan Dahl <ry@tinyclouds.org>
// All rights reserved. MIT License.
// Glossary
// outputCode = generated javascript code
// sourceCode = typescript code (or input javascript code)
// moduleName = a resolved module name
// fileName = an unresolved raw fileName.
// for http modules , its the path to the locally downloaded
// version.
import * as ts from "typescript";
import * as util from "./util";
import { log } from "./util";
import * as os from "./os";
import * as sourceMaps from "./v8_source_maps";
import { _global, globalEval } from "./globals";
import * as deno from "./deno";
const EOL = "\n";
// tslint:disable-next-line:no-any
export type AmdFactory = (...args: any[]) => undefined | object;
export type AmdDefine = (deps: string[], factory: AmdFactory) => void;
// Uncaught exceptions are sent to window.onerror by v8worker2.
// https://git.io/vhOsf
window.onerror = (message, source, lineno, colno, error) => {
// TODO Currently there is a bug in v8_source_maps.ts that causes a segfault
// if it is used within window.onerror. To workaround we uninstall the
// Error.prepareStackTrace handler. Users will get unmapped stack traces on
// uncaught exceptions until this issue is fixed.
Error.prepareStackTrace = null;
console.log(error.message, error.stack);
os.exit(1);
};
export function setup(mainJs: string, mainMap: string): void {
sourceMaps.install({
installPrepareStackTrace: true,
getGeneratedContents: (filename: string): string => {
if (filename === "/main.js") {
return mainJs;
} else if (filename === "/main.map") {
return mainMap;
} else {
const mod = FileModule.load(filename);
if (!mod) {
console.error("getGeneratedContents cannot find", filename);
}
return mod.outputCode;
}
}
});
}
// This class represents a module. We call it FileModule to make it explicit
// that each module represents a single file.
// Access to FileModule instances should only be done thru the static method
// FileModule.load(). FileModules are NOT executed upon first load, only when
// compileAndRun is called.
export class FileModule {
scriptVersion: string;
readonly exports = {};
private static readonly map = new Map<string, FileModule>();
constructor(
readonly fileName: string,
readonly sourceCode = "",
public outputCode = ""
) {
util.assert(
!FileModule.map.has(fileName),
`FileModule.map already has ${fileName}`
);
FileModule.map.set(fileName, this);
if (outputCode !== "") {
this.scriptVersion = "1";
}
}
compileAndRun(): void {
if (!this.outputCode) {
// If there is no cached outputCode, then compile the code.
util.assert(
this.sourceCode != null && this.sourceCode.length > 0,
`Have no source code from ${this.fileName}`
);
const compiler = Compiler.instance();
this.outputCode = compiler.compile(this.fileName);
os.codeCache(this.fileName, this.sourceCode, this.outputCode);
}
util.log("compileAndRun", this.sourceCode);
execute(this.fileName, this.outputCode);
}
static load(fileName: string): FileModule {
return this.map.get(fileName);
}
static getScriptsWithSourceCode(): string[] {
const out = [];
for (const fn of this.map.keys()) {
const m = this.map.get(fn);
if (m.sourceCode) {
out.push(fn);
}
}
return out;
}
}
export function makeDefine(fileName: string): AmdDefine {
const localDefine = (deps: string[], factory: AmdFactory): void => {
const localRequire = (x: string) => {
log("localRequire", x);
};
const currentModule = FileModule.load(fileName);
const localExports = currentModule.exports;
log("localDefine", fileName, deps, localExports);
const args = deps.map(dep => {
if (dep === "require") {
return localRequire;
} else if (dep === "exports") {
return localExports;
} else if (dep === "typescript") {
return ts;
} else if (dep === "deno") {
return deno;
} else {
const resolved = resolveModuleName(dep, fileName);
const depModule = FileModule.load(resolved);
depModule.compileAndRun();
return depModule.exports;
}
});
factory(...args);
};
return localDefine;
}
export function resolveModule(
moduleSpecifier: string,
containingFile: string
): null | FileModule {
//util.log("resolveModule", { moduleSpecifier, containingFile });
util.assert(moduleSpecifier != null && moduleSpecifier.length > 0);
// We ask golang to sourceCodeFetch. It will load the sourceCode and if
// there is any outputCode cached, it will return that as well.
let fetchResponse;
try {
fetchResponse = os.codeFetch(moduleSpecifier, containingFile);
} catch (e) {
// TODO Only catch "no such file or directory" errors. Need error codes.
return null;
}
const { filename, sourceCode, outputCode } = fetchResponse;
if (sourceCode.length === 0) {
return null;
}
util.log("resolveModule sourceCode length ", sourceCode.length);
const m = FileModule.load(filename);
if (m != null) {
return m;
} else {
return new FileModule(filename, sourceCode, outputCode);
}
}
function resolveModuleName(
moduleSpecifier: string,
containingFile: string
): string | undefined {
const mod = resolveModule(moduleSpecifier, containingFile);
if (mod) {
return mod.fileName;
} else {
return undefined;
}
}
function execute(fileName: string, outputCode: string): void {
util.assert(outputCode && outputCode.length > 0);
_global["define"] = makeDefine(fileName);
outputCode += `\n//# sourceURL=${fileName}`;
globalEval(outputCode);
_global["define"] = null;
}
// This is a singleton class. Use Compiler.instance() to access.
class Compiler {
options: ts.CompilerOptions = {
allowJs: true,
module: ts.ModuleKind.AMD,
outDir: "$deno$",
inlineSourceMap: true,
lib: ["es2017"],
inlineSources: true,
target: ts.ScriptTarget.ES2017
};
/*
allowJs: true,
module: ts.ModuleKind.AMD,
noEmit: false,
outDir: '$deno$',
*/
private service: ts.LanguageService;
private constructor() {
const host = new TypeScriptHost(this.options);
this.service = ts.createLanguageService(host);
}
private static _instance: Compiler;
static instance(): Compiler {
return this._instance || (this._instance = new this());
}
compile(fileName: string): string {
const output = this.service.getEmitOutput(fileName);
// Get the relevant diagnostics - this is 3x faster than
// `getPreEmitDiagnostics`.
const diagnostics = this.service
.getCompilerOptionsDiagnostics()
.concat(this.service.getSyntacticDiagnostics(fileName))
.concat(this.service.getSemanticDiagnostics(fileName));
if (diagnostics.length > 0) {
const errMsg = ts.formatDiagnosticsWithColorAndContext(
diagnostics,
formatDiagnosticsHost
);
console.log(errMsg);
os.exit(1);
}
util.assert(!output.emitSkipped);
const outputCode = output.outputFiles[0].text;
// let sourceMapCode = output.outputFiles[0].text;
return outputCode;
}
}
// Create the compiler host for type checking.
class TypeScriptHost implements ts.LanguageServiceHost {
constructor(readonly options: ts.CompilerOptions) {}
getScriptFileNames(): string[] {
const keys = FileModule.getScriptsWithSourceCode();
util.log("getScriptFileNames", keys);
return keys;
}
getScriptVersion(fileName: string): string {
util.log("getScriptVersion", fileName);
const m = FileModule.load(fileName);
return m.scriptVersion;
}
getScriptSnapshot(fileName: string): ts.IScriptSnapshot | undefined {
util.log("getScriptSnapshot", fileName);
const m = resolveModule(fileName, ".");
if (m == null) {
util.log("getScriptSnapshot", fileName, "NOT FOUND");
return undefined;
}
//const m = resolveModule(fileName, ".");
util.assert(m.sourceCode.length > 0);
return ts.ScriptSnapshot.fromString(m.sourceCode);
}
fileExists(fileName: string): boolean {
const m = resolveModule(fileName, ".");
const exists = m != null;
util.log("fileExist", fileName, exists);
return exists;
}
readFile(path: string, encoding?: string): string | undefined {
util.log("readFile", path);
throw Error("not implemented");
}
getNewLine() {
return EOL;
}
getCurrentDirectory() {
util.log("getCurrentDirectory");
return ".";
}
getCompilationSettings() {
util.log("getCompilationSettings");
return this.options;
}
getDefaultLibFileName(options: ts.CompilerOptions): string {
const fn = ts.getDefaultLibFileName(options);
util.log("getDefaultLibFileName", fn);
const m = resolveModule(fn, "/$asset$/");
return m.fileName;
}
resolveModuleNames(
moduleNames: string[],
containingFile: string,
reusedNames?: string[]
): Array<ts.ResolvedModule | undefined> {
//util.log("resolveModuleNames", { moduleNames, reusedNames });
return moduleNames.map((name: string) => {
let resolvedFileName;
if (name === "deno") {
resolvedFileName = resolveModuleName("deno.d.ts", "/$asset$/");
} else if (name === "typescript") {
resolvedFileName = resolveModuleName("typescript.d.ts", "/$asset$/");
} else {
resolvedFileName = resolveModuleName(name, containingFile);
if (resolvedFileName == null) {
return undefined;
}
}
const isExternalLibraryImport = false;
return { resolvedFileName, isExternalLibraryImport };
});
}
}
const formatDiagnosticsHost: ts.FormatDiagnosticsHost = {
getCurrentDirectory(): string {
return ".";
},
getCanonicalFileName(fileName: string): string {
return fileName;
},
getNewLine(): string {
return EOL;
}
};

126
js/tests.ts Normal file
View file

@ -0,0 +1,126 @@
// Copyright 2018 Ryan Dahl <ry@tinyclouds.org>
// All rights reserved. MIT License.
// This test is executed as part of integration_test.go
// But it can also be run manually:
// ./deno tests.ts
// There must also be a static file http server running on localhost:4545
// serving the deno project directory. Try this:
// http-server -p 4545 --cors .
import { test, assert, assertEqual } from "./testing/testing.ts";
import { readFileSync, writeFileSync } from "deno";
test(async function tests_test() {
assert(true);
});
test(async function tests_fetch() {
const response = await fetch("http://localhost:4545/package.json");
const json = await response.json();
assertEqual(json.name, "deno");
});
test(function tests_console_assert() {
console.assert(true);
let hasThrown = false;
try {
console.assert(false);
} catch {
hasThrown = true;
}
assertEqual(hasThrown, true);
});
test(async function tests_readFileSync() {
const data = readFileSync("package.json");
if (!data.byteLength) {
throw Error(
`Expected positive value for data.byteLength ${data.byteLength}`
);
}
const decoder = new TextDecoder("utf-8");
const json = decoder.decode(data);
const pkg = JSON.parse(json);
assertEqual(pkg.name, "deno");
});
test(async function tests_writeFileSync() {
const enc = new TextEncoder();
const data = enc.encode("Hello");
// TODO need ability to get tmp dir.
// const fn = "/tmp/test.txt";
writeFileSync("/tmp/test.txt", data, 0o666);
const dataRead = readFileSync("/tmp/test.txt");
const dec = new TextDecoder("utf-8");
const actual = dec.decode(dataRead);
assertEqual("Hello", actual);
});
test(function tests_console_assert() {
console.assert(true);
let hasThrown = false;
try {
console.assert(false);
} catch {
hasThrown = true;
}
assertEqual(hasThrown, true);
});
test(function tests_console_stringify_circular() {
class Base {
a = 1;
m1() {}
}
class Extended extends Base {
b = 2;
m2() {}
}
// tslint:disable-next-line:no-any
const nestedObj: any = {
num: 1,
bool: true,
str: "a",
method() {},
un: undefined,
nu: null,
arrowFunc: () => {},
extendedClass: new Extended(),
nFunc: new Function(),
extendedCstr: Extended
};
const circularObj = {
num: 2,
bool: false,
str: "b",
method() {},
un: undefined,
nu: null,
nested: nestedObj,
emptyObj: {},
arr: [1, "s", false, null, nestedObj],
baseClass: new Base()
};
nestedObj.o = circularObj;
try {
console.log(1);
console.log("s");
console.log(false);
console.log(Symbol(1));
console.log(null);
console.log(undefined);
console.log(new Extended());
console.log(function f() {});
console.log(nestedObj);
console.log(JSON);
console.log(console);
} catch {
throw new Error("Expected no crash on circular object");
}
});

6
js/text-encoding.d.ts vendored Normal file
View file

@ -0,0 +1,6 @@
// Remove and depend on @types/text-encoding once this PR is merged
// https://github.com/DefinitelyTyped/DefinitelyTyped/pull/26141
declare module "text-encoding" {
export const TextEncoder: TextEncoder;
export const TextDecoder: TextDecoder;
}

89
js/timers.ts Normal file
View file

@ -0,0 +1,89 @@
// Copyright 2018 Ryan Dahl <ry@tinyclouds.org>
// All rights reserved. MIT License.
import { deno as pb } from "./msg.pb";
import { pubInternal, sub } from "./dispatch";
import { assert } from "./util";
let nextTimerId = 1;
// tslint:disable-next-line:no-any
export type TimerCallback = (...args: any[]) => void;
interface Timer {
id: number;
cb: TimerCallback;
interval: boolean;
// tslint:disable-next-line:no-any
args: any[];
delay: number; // milliseconds
}
const timers = new Map<number, Timer>();
export function initTimers() {
sub("timers", onMessage);
}
function onMessage(payload: Uint8Array) {
const msg = pb.Msg.decode(payload);
assert(msg.command === pb.Msg.Command.TIMER_READY);
const { timerReadyId, timerReadyDone } = msg;
const timer = timers.get(timerReadyId);
if (!timer) {
return;
}
timer.cb(...timer.args);
if (timerReadyDone) {
timers.delete(timerReadyId);
}
}
function setTimer(
cb: TimerCallback,
delay: number,
interval: boolean,
// tslint:disable-next-line:no-any
args: any[]
): number {
const timer = {
id: nextTimerId++,
interval,
delay,
args,
cb
};
timers.set(timer.id, timer);
pubInternal("timers", {
command: pb.Msg.Command.TIMER_START,
timerStartId: timer.id,
timerStartInterval: timer.interval,
timerStartDelay: timer.delay
});
return timer.id;
}
export function setTimeout(
cb: TimerCallback,
delay: number,
// tslint:disable-next-line:no-any
...args: any[]
): number {
return setTimer(cb, delay, false, args);
}
export function setInterval(
cb: TimerCallback,
delay: number,
// tslint:disable-next-line:no-any
...args: any[]
): number {
return setTimer(cb, delay, true, args);
}
export function clearTimer(id: number) {
timers.delete(id);
pubInternal("timers", {
command: pb.Msg.Command.TIMER_CLEAR,
timerClearId: id
});
}

10
js/types.ts Normal file
View file

@ -0,0 +1,10 @@
// Copyright 2018 Ryan Dahl <ry@tinyclouds.org>
// All rights reserved. MIT License.
export type TypedArray = Uint8Array | Float32Array | Int32Array;
export interface ModuleInfo {
moduleName?: string;
filename?: string;
sourceCode?: string;
outputCode?: string;
}

53
js/util.ts Normal file
View file

@ -0,0 +1,53 @@
// Copyright 2018 Ryan Dahl <ry@tinyclouds.org>
// All rights reserved. MIT License.
import { debug } from "./main";
import { TypedArray } from "./types";
// Internal logging for deno. Use the "debug" variable above to control
// output.
// tslint:disable-next-line:no-any
export function log(...args: any[]): void {
if (debug) {
console.log(...args);
}
}
export function assert(cond: boolean, msg = "") {
if (!cond) {
throw Error(`Assert fail. ${msg}`);
}
}
export function typedArrayToArrayBuffer(ta: TypedArray): ArrayBuffer {
const ab = ta.buffer.slice(ta.byteOffset, ta.byteOffset + ta.byteLength);
return ab as ArrayBuffer;
}
export function arrayToStr(ui8: Uint8Array): string {
return String.fromCharCode(...ui8);
}
// A `Resolvable` is a Promise with the `reject` and `resolve` functions
// placed as methods on the promise object itself. It allows you to do:
//
// const p = createResolvable<number>();
// ...
// p.resolve(42);
//
// It'd be prettier to make Resolvable a class that inherits from Promise,
// rather than an interface. This is possible in ES2016, however typescript
// produces broken code when targeting ES5 code.
// See https://github.com/Microsoft/TypeScript/issues/15202
// At the time of writing, the github issue is closed but the problem remains.
export interface Resolvable<T> extends Promise<T> {
resolve: (value?: T | PromiseLike<T>) => void;
// tslint:disable-next-line:no-any
reject: (reason?: any) => void;
}
export function createResolvable<T>(): Resolvable<T> {
let methods;
const promise = new Promise<T>((resolve, reject) => {
methods = { resolve, reject };
});
return Object.assign(promise, methods) as Resolvable<T>;
}

273
js/v8_source_maps.ts Normal file
View file

@ -0,0 +1,273 @@
// Copyright 2014 Evan Wallace
// Copyright 2018 Ryan Dahl <ry@tinyclouds.org>
// All rights reserved. MIT License.
// Originated from source-map-support but has been heavily modified for deno.
import { SourceMapConsumer, MappedPosition } from "source-map";
import * as base64 from "base64-js";
import { arrayToStr } from "./util";
const consumers = new Map<string, SourceMapConsumer>();
interface Options {
// A callback the returns generated file contents.
getGeneratedContents: GetGeneratedContentsCallback;
// Usually set the following to true. Set to false for testing.
installPrepareStackTrace: boolean;
}
interface CallSite extends NodeJS.CallSite {
getScriptNameOrSourceURL(): string;
}
interface Position {
source: string; // Filename
column: number;
line: number;
}
type GetGeneratedContentsCallback = (fileName: string) => string;
let getGeneratedContents: GetGeneratedContentsCallback;
export function install(options: Options) {
getGeneratedContents = options.getGeneratedContents;
if (options.installPrepareStackTrace) {
Error.prepareStackTrace = prepareStackTraceWrapper;
}
}
export function prepareStackTraceWrapper(
error: Error,
stack: CallSite[]
): string {
try {
return prepareStackTrace(error, stack);
} catch (prepareStackError) {
Error.prepareStackTrace = null;
console.log("=====Error inside of prepareStackTrace====");
console.log(prepareStackError.stack.toString());
console.log("=====Original error=======================");
throw error;
}
}
export function prepareStackTrace(error: Error, stack: CallSite[]): string {
const frames = stack.map(
(frame: CallSite) => `\n at ${wrapCallSite(frame).toString()}`
);
return error.toString() + frames.join("");
}
export function wrapCallSite(frame: CallSite): CallSite {
if (frame.isNative()) {
return frame;
}
// Most call sites will return the source file from getFileName(), but code
// passed to eval() ending in "//# sourceURL=..." will return the source file
// from getScriptNameOrSourceURL() instead
const source = frame.getFileName() || frame.getScriptNameOrSourceURL();
if (source) {
const line = frame.getLineNumber();
const column = frame.getColumnNumber() - 1;
const position = mapSourcePosition({ source, line, column });
frame = cloneCallSite(frame);
frame.getFileName = () => position.source;
frame.getLineNumber = () => position.line;
frame.getColumnNumber = () => Number(position.column) + 1;
frame.getScriptNameOrSourceURL = () => position.source;
frame.toString = () => CallSiteToString(frame);
return frame;
}
// Code called using eval() needs special handling
let origin = frame.isEval() && frame.getEvalOrigin();
if (origin) {
origin = mapEvalOrigin(origin);
frame = cloneCallSite(frame);
frame.getEvalOrigin = () => origin;
return frame;
}
// If we get here then we were unable to change the source position
return frame;
}
function cloneCallSite(frame: CallSite): CallSite {
// tslint:disable:no-any
const obj: any = {};
const frame_ = frame as any;
const props = Object.getOwnPropertyNames(Object.getPrototypeOf(frame));
props.forEach(name => {
obj[name] = /^(?:is|get)/.test(name)
? () => frame_[name].call(frame)
: frame_[name];
});
return (obj as any) as CallSite;
// tslint:enable:no-any
}
// Taken from source-map-support, original copied from V8's messages.js
// MIT License. Copyright (c) 2014 Evan Wallace
function CallSiteToString(frame: CallSite): string {
let fileName;
let fileLocation = "";
if (frame.isNative()) {
fileLocation = "native";
} else {
fileName = frame.getScriptNameOrSourceURL();
if (!fileName && frame.isEval()) {
fileLocation = frame.getEvalOrigin();
fileLocation += ", "; // Expecting source position to follow.
}
if (fileName) {
fileLocation += fileName;
} else {
// Source code does not originate from a file and is not native, but we
// can still get the source position inside the source string, e.g. in
// an eval string.
fileLocation += "<anonymous>";
}
const lineNumber = frame.getLineNumber();
if (lineNumber != null) {
fileLocation += ":" + String(lineNumber);
const columnNumber = frame.getColumnNumber();
if (columnNumber) {
fileLocation += ":" + String(columnNumber);
}
}
}
let line = "";
const functionName = frame.getFunctionName();
let addSuffix = true;
const isConstructor = frame.isConstructor();
const isMethodCall = !(frame.isToplevel() || isConstructor);
if (isMethodCall) {
let typeName = frame.getTypeName();
// Fixes shim to be backward compatable with Node v0 to v4
if (typeName === "[object Object]") {
typeName = "null";
}
const methodName = frame.getMethodName();
if (functionName) {
if (typeName && functionName.indexOf(typeName) !== 0) {
line += typeName + ".";
}
line += functionName;
if (
methodName &&
functionName.indexOf("." + methodName) !==
functionName.length - methodName.length - 1
) {
line += ` [as ${methodName} ]`;
}
} else {
line += typeName + "." + (methodName || "<anonymous>");
}
} else if (isConstructor) {
line += "new " + (functionName || "<anonymous>");
} else if (functionName) {
line += functionName;
} else {
line += fileLocation;
addSuffix = false;
}
if (addSuffix) {
line += ` (${fileLocation})`;
}
return line;
}
// Regex for detecting source maps
const reSourceMap = /^data:application\/json[^,]+base64,/;
function loadConsumer(source: string): SourceMapConsumer {
let consumer = consumers.get(source);
if (consumer == null) {
const code = getGeneratedContents(source);
if (!code) {
return null;
}
let sourceMappingURL = retrieveSourceMapURL(code);
if (!sourceMappingURL) {
throw Error("No source map?");
}
let sourceMapData: string;
if (reSourceMap.test(sourceMappingURL)) {
// Support source map URL as a data url
const rawData = sourceMappingURL.slice(sourceMappingURL.indexOf(",") + 1);
const ui8 = base64.toByteArray(rawData);
sourceMapData = arrayToStr(ui8);
sourceMappingURL = source;
} else {
// Support source map URLs relative to the source URL
//sourceMappingURL = supportRelativeURL(source, sourceMappingURL);
sourceMapData = getGeneratedContents(sourceMappingURL);
}
//console.log("sourceMapData", sourceMapData);
const rawSourceMap = JSON.parse(sourceMapData);
consumer = new SourceMapConsumer(rawSourceMap);
consumers.set(source, consumer);
}
return consumer;
}
function retrieveSourceMapURL(fileData: string): string {
// Get the URL of the source map
// tslint:disable-next-line:max-line-length
const re = /(?:\/\/[@#][ \t]+sourceMappingURL=([^\s'"]+?)[ \t]*$)|(?:\/\*[@#][ \t]+sourceMappingURL=([^\*]+?)[ \t]*(?:\*\/)[ \t]*$)/gm;
// Keep executing the search to find the *last* sourceMappingURL to avoid
// picking up sourceMappingURLs from comments, strings, etc.
let lastMatch, match;
while ((match = re.exec(fileData))) {
lastMatch = match;
}
if (!lastMatch) {
return null;
}
return lastMatch[1];
}
function mapSourcePosition(position: Position): MappedPosition {
const consumer = loadConsumer(position.source);
if (consumer == null) {
return position;
}
const mapped = consumer.originalPositionFor(position);
return mapped;
}
// Parses code generated by FormatEvalOrigin(), a function inside V8:
// https://code.google.com/p/v8/source/browse/trunk/src/messages.js
function mapEvalOrigin(origin: string): string {
// Most eval() calls are in this format
let match = /^eval at ([^(]+) \((.+):(\d+):(\d+)\)$/.exec(origin);
if (match) {
const position = mapSourcePosition({
source: match[2],
line: Number(match[3]),
column: Number(match[4]) - 1
});
const pos = [
position.source,
position.line,
Number(position.column) + 1
].join(":");
return `eval at ${match[1]} (${pos})`;
}
// Parse nested eval() calls using recursion
match = /^eval at ([^(]+) \((.+)\)$/.exec(origin);
if (match) {
return `eval at ${match[1]} (${mapEvalOrigin(match[2])})`;
}
// Make sure we still return useful information if we didn't find anything
return origin;
}

View file

@ -13,6 +13,6 @@
"allowUnreachableCode": false,
"experimentalDecorators": true
},
"include": ["js/**/*.ts", "js/**/*.js"],
"exclude": ["js/mock_runtime.js", "js/node_modules"]
"include": [ "js/main.ts" ],
"exclude": [ "node_modules" ]
}

View file

@ -1,77 +1,59 @@
{
"extends": [
"tslint-eslint-rules", "tslint-no-circular-imports"
],
"rules": {
"align": [
true,
// TODO "arguments",
// TODO "elements",
"members",
"parameters",
"statements"
],
"array-bracket-spacing": [true, "never"],
"array-type": [true, "array-simple"],
"arrow-return-shorthand": true,
"ban": [true,
{"name": "parseInt", "message": "tsstyle#type-coercion"},
{"name": "parseFloat", "message": "tsstyle#type-coercion"},
{"name": "Array", "message": "tsstyle#array-constructor"}
"ban": [
true,
["fit"],
["fdescribe"],
["xit"],
["xdescribe"],
["fitAsync"],
["xitAsync"],
["fitFakeAsync"],
["xitFakeAsync"]
],
"ban-types": [true,
"ban-types": [
true,
["Object", "Use {} instead."],
["String", "Use 'string' instead."],
["Number", "Use 'number' instead."],
["Boolean", "Use 'boolean' instead."]
],
"brace-style": [true, "1tbs", { "allowSingleLine": true }],
"block-spacing": [true, "always"],
"class-name": true,
"comment-format": [true, "check-space"],
"curly": [true, "ignore-same-line"],
"eofline": true,
"forin": true,
"ter-func-call-spacing": true,
"indent": [true, "spaces", 2],
"curly": true,
"interface-name": [true, "never-prefix"],
"jsdoc-format": true,
"forin": false,
"label-position": true,
"linebreak-style": [true, "LF"],
"max-line-length": { "options": [80] },
"member-access": [true, "no-public"],
"max-line-length": [true, 80],
"new-parens": true,
"no-angle-bracket-type-assertion": true,
//"no-any": true,
"no-arg": true,
"no-conditional-assignment": true,
"no-consecutive-blank-lines": true,
"no-any": true,
"no-construct": true,
"no-consecutive-blank-lines": true,
"no-debugger": true,
"no-default-export": true,
"no-duplicate-variable": true,
"no-inferrable-types": true,
"no-irregular-whitespace": true,
"no-namespace": [true, "allow-declarations"],
"no-multi-spaces": true,
//"no-namespace": [true, "allow-declarations"],
"no-reference": true,
"no-require-imports": true,
"no-string-throw": true,
"no-trailing-whitespace": true,
"no-unused-expression": true,
"no-unused-variable": true,
"no-var-keyword": true,
"object-literal-shorthand": true,
"object-literal-sort-keys": [true, "match-declaration-order"],
"one-line": [true, "check-catch", "check-finally", "check-else"],
//"only-arrow-functions": [true, "allow-declarations", "allow-named-functions"],
"ordered-imports": true,
"quotemark": [true, "double"],
"only-arrow-functions": [
true,
"allow-declarations",
"allow-named-functions"
],
"prefer-const": true,
"quotemark": [true, "double"],
"radix": true,
// https://github.com/buzinas/tslint-eslint-rules/issues/318
// "space-before-blocks": true,
"space-before-function-paren": false,
"restrict-plus-operands": true,
"semicolon": [true, "always", "ignore-bound-class-methods"],
//"switch-default": true,
"switch-default": true,
"triple-equals": [true, "allow-null-check"],
"use-isnan": true,
"variable-name": [
@ -80,29 +62,6 @@
"ban-keywords",
"allow-leading-underscore",
"allow-trailing-underscore"
],
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-module",
"check-separator",
"check-rest-spread",
"check-type",
"check-typecast",
"check-type-operator",
"check-preblock"
]
},
"jsRules": {
"max-line-length": { "options": [80] },
"linebreak-style": [true, "LF"],
"indent": [true, "spaces", 2]
},
"linterOptions": {
"exclude": [
"js/msg.pb.js"
]
}
}