1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-25 16:49:18 -05:00

runtime.ts refactor into compiler.ts (#564)

Adds compiler_test.ts
This commit is contained in:
Ryan Dahl 2018-08-22 17:17:26 -04:00 committed by GitHub
parent c5bb412933
commit e7cab71574
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 1154 additions and 396 deletions

View file

@ -162,6 +162,7 @@ executable("snapshot_creator") {
run_node("gen_declarations") { run_node("gen_declarations") {
out_dir = target_gen_dir out_dir = target_gen_dir
sources = [ sources = [
"js/compiler.ts",
"js/console.ts", "js/console.ts",
"js/deno.ts", "js/deno.ts",
"js/globals.ts", "js/globals.ts",
@ -171,6 +172,7 @@ run_node("gen_declarations") {
"js/util.ts", "js/util.ts",
] ]
outputs = [ outputs = [
out_dir + "/js/compiler.d.ts",
out_dir + "/js/console.d.ts", out_dir + "/js/console.d.ts",
out_dir + "/js/deno.d.ts", out_dir + "/js/deno.d.ts",
out_dir + "/js/globals.d.ts", out_dir + "/js/globals.d.ts",
@ -196,6 +198,7 @@ run_node("bundle") {
out_dir = "$target_gen_dir/bundle/" out_dir = "$target_gen_dir/bundle/"
sources = [ sources = [
"js/assets.ts", "js/assets.ts",
"js/compiler.ts",
"js/console.ts", "js/console.ts",
"js/fetch.ts", "js/fetch.ts",
"js/fetch_types.d.ts", "js/fetch_types.d.ts",
@ -204,7 +207,6 @@ run_node("bundle") {
"js/main.ts", "js/main.ts",
"js/os.ts", "js/os.ts",
"js/plugins.d.ts", "js/plugins.d.ts",
"js/runtime.ts",
"js/text_encoding.ts", "js/text_encoding.ts",
"js/timers.ts", "js/timers.ts",
"js/types.d.ts", "js/types.d.ts",

View file

@ -7,6 +7,7 @@
// tslint:disable:max-line-length // tslint:disable:max-line-length
// Generated definitions // Generated definitions
import compilerDts from "gen/js/compiler.d.ts!string";
import consoleDts from "gen/js/console.d.ts!string"; import consoleDts from "gen/js/console.d.ts!string";
import denoDts from "gen/js/deno.d.ts!string"; import denoDts from "gen/js/deno.d.ts!string";
import globalsDts from "gen/js/globals.d.ts!string"; import globalsDts from "gen/js/globals.d.ts!string";
@ -55,6 +56,7 @@ import fetchTypesDts from "/js/fetch_types.d.ts!string";
// prettier-ignore // prettier-ignore
export const assetSourceCode: { [key: string]: string } = { export const assetSourceCode: { [key: string]: string } = {
// Generated definitions // Generated definitions
"compiler.d.ts": compilerDts,
"console.d.ts": consoleDts, "console.d.ts": consoleDts,
"deno.d.ts": denoDts, "deno.d.ts": denoDts,
"globals.d.ts": globalsDts, "globals.d.ts": globalsDts,

595
js/compiler.ts Normal file
View file

@ -0,0 +1,595 @@
// Copyright 2018 the Deno authors. All rights reserved. MIT license.
import * as ts from "typescript";
import { assetSourceCode } from "./assets";
import * as deno from "./deno";
import { libdeno, window, globalEval } from "./globals";
import * as os from "./os";
import { RawSourceMap } from "./types";
import { assert, log, notImplemented } from "./util";
import * as sourceMaps from "./v8_source_maps";
const EOL = "\n";
const ASSETS = "$asset$";
// tslint:disable:no-any
type AmdCallback = (...args: any[]) => void;
type AmdErrback = (err: any) => void;
export type AmdFactory = (...args: any[]) => object | void;
// tslint:enable:no-any
export type AmdDefine = (deps: string[], factory: AmdFactory) => void;
type AmdRequire = (
deps: string[],
callback: AmdCallback,
errback?: AmdErrback
) => void;
// The location that a module is being loaded from. This could be a directory,
// like ".", or it could be a module specifier like
// "http://gist.github.com/somefile.ts"
type ContainingFile = string;
// The internal local filename of a compiled module. It will often be something
// like "/home/ry/.deno/gen/f7b4605dfbc4d3bb356e98fda6ceb1481e4a8df5.js"
type ModuleFileName = string;
// The external name of a module - could be a URL or could be a relative path.
// Examples "http://gist.github.com/somefile.ts" or "./somefile.ts"
type ModuleSpecifier = string;
// The compiled source code which is cached in .deno/gen/
type OutputCode = string;
/**
* Abstraction of the APIs required from the `os` module so they can be
* easily mocked.
*/
export interface Os {
codeCache: typeof os.codeCache;
codeFetch: typeof os.codeFetch;
exit: typeof os.exit;
}
/**
* Abstraction of the APIs required from the `typescript` module so they can
* be easily mocked.
*/
export interface Ts {
createLanguageService: typeof ts.createLanguageService;
/* tslint:disable-next-line:max-line-length */
formatDiagnosticsWithColorAndContext: typeof ts.formatDiagnosticsWithColorAndContext;
}
/**
* A simple object structure for caching resolved modules and their contents.
*
* Named `ModuleMetaData` to clarify it is just a representation of meta data of
* the module, not the actual module instance.
*/
export class ModuleMetaData {
public readonly exports = {};
public scriptSnapshot?: ts.IScriptSnapshot;
public scriptVersion = "";
constructor(
public readonly fileName: string,
public readonly sourceCode = "",
public outputCode = ""
) {
if (outputCode !== "" || fileName.endsWith(".d.ts")) {
this.scriptVersion = "1";
}
}
}
/**
* The required minimal API to allow formatting of TypeScript compiler
* diagnostics.
*/
const formatDiagnosticsHost: ts.FormatDiagnosticsHost = {
getCurrentDirectory: () => ".",
getCanonicalFileName: (fileName: string) => fileName,
getNewLine: () => EOL
};
/**
* Throw a module resolution error, when a module is unsuccessfully resolved.
*/
function throwResolutionError(
message: string,
moduleSpecifier: ModuleSpecifier,
containingFile: ContainingFile
): never {
throw new Error(
// tslint:disable-next-line:max-line-length
`Cannot resolve module "${moduleSpecifier}" from "${containingFile}".\n ${message}`
);
}
// ts.ScriptKind is not available at runtime, so local enum definition
enum ScriptKind {
JS = 1,
TS = 3,
JSON = 6
}
/**
* A singleton class that combines the TypeScript Language Service host API
* with Deno specific APIs to provide an interface for compiling and running
* TypeScript and JavaScript modules.
*/
export class DenoCompiler implements ts.LanguageServiceHost {
// Modules are usually referenced by their ModuleSpecifier and ContainingFile,
// and keeping a map of the resolved module file name allows more efficient
// future resolution
private readonly _fileNamesMap = new Map<
ContainingFile,
Map<ModuleSpecifier, ModuleFileName>
>();
// A reference to global eval, so it can be monkey patched during testing
private _globalEval = globalEval;
// A reference to the log utility, so it can be monkey patched during testing
private _log = log;
// A map of module file names to module meta data
private readonly _moduleMetaDataMap = new Map<
ModuleFileName,
ModuleMetaData
>();
// TODO ideally this are not static and can be influenced by command line
// arguments
private readonly _options: Readonly<ts.CompilerOptions> = {
allowJs: true,
module: ts.ModuleKind.AMD,
outDir: "$deno$",
// TODO https://github.com/denoland/deno/issues/23
inlineSourceMap: true,
inlineSources: true,
stripComments: true,
target: ts.ScriptTarget.ESNext
};
// A reference to the `./os.ts` module, so it can be monkey patched during
// testing
private _os: Os = os;
// Used to contain the script file we are currently running
private _scriptFileNames: string[] = [];
// A reference to the TypeScript LanguageService instance so it can be
// monkey patched during testing
private _service: ts.LanguageService;
// A reference to `typescript` module so it can be monkey patched during
// testing
private _ts: Ts = ts;
// A reference to the global scope so it can be monkey patched during
// testing
private _window = window;
/**
* The TypeScript language service often refers to the resolved fileName of
* a module, this is a shortcut to avoid unnecessary module resolution logic
* for modules that may have been initially resolved by a `moduleSpecifier`
* and `containingFile`. Also, `resolveModule()` throws when the module
* cannot be resolved, which isn't always valid when dealing with the
* TypeScript compiler, but the TypeScript compiler shouldn't be asking about
* external modules that we haven't told it about yet.
*/
private _getModuleMetaData(
fileName: ModuleFileName
): ModuleMetaData | undefined {
return this._moduleMetaDataMap.has(fileName)
? this._moduleMetaDataMap.get(fileName)
: fileName.startsWith(ASSETS)
? this.resolveModule(fileName, "")
: undefined;
}
/**
* Setup being able to map back source references back to their source
*
* TODO is this the best place for this? It is tightly coupled to how the
* compiler works, but it is also tightly coupled to how the whole runtime
* environment is bootstrapped. It also needs efficient access to the
* `outputCode` of the module information, which exists inside of the
* compiler instance.
*/
private _setupSourceMaps(): void {
sourceMaps.install({
installPrepareStackTrace: true,
getGeneratedContents: (fileName: string): string | RawSourceMap => {
this._log("getGeneratedContents", fileName);
if (fileName === "gen/bundle/main.js") {
assert(libdeno.mainSource.length > 0);
return libdeno.mainSource;
} else if (fileName === "main.js.map") {
return libdeno.mainSourceMap;
} else if (fileName === "deno_main.js") {
return "";
} else {
const moduleMetaData = this._moduleMetaDataMap.get(fileName);
if (!moduleMetaData) {
this._log("getGeneratedContents cannot find", fileName);
return "";
}
return moduleMetaData.outputCode;
}
}
});
}
private constructor() {
if (DenoCompiler._instance) {
throw new TypeError("Attempt to create an additional compiler.");
}
this._service = this._ts.createLanguageService(this);
this._setupSourceMaps();
}
// Deno specific compiler API
/**
* Retrieve the output of the TypeScript compiler for a given `fileName`.
*/
compile(fileName: ModuleFileName): OutputCode {
const service = this._service;
const output = service.getEmitOutput(fileName);
// Get the relevant diagnostics - this is 3x faster than
// `getPreEmitDiagnostics`.
const diagnostics = [
...service.getCompilerOptionsDiagnostics(),
...service.getSyntacticDiagnostics(fileName),
...service.getSemanticDiagnostics(fileName)
];
if (diagnostics.length > 0) {
const errMsg = this._ts.formatDiagnosticsWithColorAndContext(
diagnostics,
formatDiagnosticsHost
);
console.log(errMsg);
// All TypeScript errors are terminal for deno
this._os.exit(1);
}
assert(!output.emitSkipped, "The emit was skipped for an unknown reason.");
// Currently we are inlining source maps, there should be only 1 output file
// See: https://github.com/denoland/deno/issues/23
assert(
output.outputFiles.length === 1,
"Only single file should be output."
);
const [outputFile] = output.outputFiles;
return outputFile.text;
}
/**
* Create a localized AMD `define` function and return it.
*/
makeDefine(moduleMetaData: ModuleMetaData): AmdDefine {
const localDefine = (deps: string[], factory: AmdFactory): void => {
// TypeScript will emit a local require dependency when doing dynamic
// `import()`
const localRequire: AmdRequire = (
deps: string[],
callback: AmdCallback,
errback?: AmdErrback
): void => {
this._log("localRequire", deps);
try {
const args = deps.map(dep => {
if (dep in DenoCompiler._builtins) {
return DenoCompiler._builtins[dep];
} else {
const depModuleMetaData = this.run(dep, moduleMetaData.fileName);
return depModuleMetaData.exports;
}
});
callback(...args);
} catch (e) {
if (errback) {
errback(e);
} else {
throw e;
}
}
};
const localExports = moduleMetaData.exports;
this._log("localDefine", moduleMetaData.fileName, deps, localExports);
const args = deps.map(dep => {
if (dep === "require") {
return localRequire;
} else if (dep === "exports") {
return localExports;
} else if (dep in DenoCompiler._builtins) {
return DenoCompiler._builtins[dep];
} else {
const depModuleMetaData = this.run(dep, moduleMetaData.fileName);
return depModuleMetaData.exports;
}
});
factory(...args);
};
return localDefine;
}
/**
* Given a `moduleSpecifier` and `containingFile` retrieve the cached
* `fileName` for a given module. If the module has yet to be resolved
* this will return `undefined`.
*/
resolveFileName(
moduleSpecifier: ModuleSpecifier,
containingFile: ContainingFile
): ModuleFileName | undefined {
this._log("resolveFileName", { moduleSpecifier, containingFile });
const innerMap = this._fileNamesMap.get(containingFile);
if (innerMap) {
return innerMap.get(moduleSpecifier);
}
return undefined;
}
/**
* Given a `moduleSpecifier` and `containingFile`, resolve the module and
* return the `ModuleMetaData`.
*/
resolveModule(
moduleSpecifier: ModuleSpecifier,
containingFile: ContainingFile
): ModuleMetaData {
this._log("resolveModule", { moduleSpecifier, containingFile });
assert(moduleSpecifier != null && moduleSpecifier.length > 0);
let fileName = this.resolveFileName(moduleSpecifier, containingFile);
if (fileName && this._moduleMetaDataMap.has(fileName)) {
return this._moduleMetaDataMap.get(fileName)!;
}
let sourceCode: string | undefined;
let outputCode: string | undefined;
if (
moduleSpecifier.startsWith(ASSETS) ||
containingFile.startsWith(ASSETS)
) {
// Assets are compiled into the runtime javascript bundle.
// we _know_ `.pop()` will return a string, but TypeScript doesn't so
// not null assertion
const moduleId = moduleSpecifier.split("/").pop()!;
const assetName = moduleId.includes(".") ? moduleId : `${moduleId}.d.ts`;
assert(assetName in assetSourceCode, `No such asset "${assetName}"`);
sourceCode = assetSourceCode[assetName];
fileName = `${ASSETS}/${assetName}`;
} else {
// We query Rust with a CodeFetch message. It will load the sourceCode,
// and if there is any outputCode cached, will return that as well.
let fetchResponse;
try {
fetchResponse = this._os.codeFetch(moduleSpecifier, containingFile);
} catch (e) {
return throwResolutionError(
`os.codeFetch message: ${e.message}`,
moduleSpecifier,
containingFile
);
}
fileName = fetchResponse.filename || undefined;
sourceCode = fetchResponse.sourceCode || undefined;
outputCode = fetchResponse.outputCode || undefined;
}
if (!sourceCode || sourceCode.length === 0 || !fileName) {
return throwResolutionError(
"Invalid source code or file name.",
moduleSpecifier,
containingFile
);
}
this._log("resolveModule sourceCode length ", sourceCode.length);
this.setFileName(moduleSpecifier, containingFile, fileName);
if (fileName && this._moduleMetaDataMap.has(fileName)) {
return this._moduleMetaDataMap.get(fileName)!;
}
const moduleMetaData = new ModuleMetaData(fileName, sourceCode, outputCode);
this._moduleMetaDataMap.set(fileName, moduleMetaData);
return moduleMetaData;
}
/**
* Resolve the `fileName` for a given `moduleSpecifier` and `containingFile`
*/
resolveModuleName(
moduleSpecifier: ModuleSpecifier,
containingFile: ContainingFile
): ModuleFileName | undefined {
const moduleMetaData = this.resolveModule(moduleSpecifier, containingFile);
return moduleMetaData ? moduleMetaData.fileName : undefined;
}
/* tslint:disable-next-line:no-any */
/**
* Execute a module based on the `moduleSpecifier` and the `containingFile`
* and return the resulting `FileModule`.
*/
run(
moduleSpecifier: ModuleSpecifier,
containingFile: ContainingFile
): ModuleMetaData {
this._log("run", { moduleSpecifier, containingFile });
const moduleMetaData = this.resolveModule(moduleSpecifier, containingFile);
const fileName = moduleMetaData.fileName;
this._scriptFileNames = [fileName];
const sourceCode = moduleMetaData.sourceCode;
let outputCode = moduleMetaData.outputCode;
if (!outputCode) {
outputCode = moduleMetaData.outputCode = `${this.compile(
fileName
)}\n//# sourceURL=${fileName}`;
moduleMetaData!.scriptVersion = "1";
this._os.codeCache(fileName, sourceCode, outputCode);
}
this._window.define = this.makeDefine(moduleMetaData);
this._globalEval(moduleMetaData.outputCode);
this._window.define = undefined;
return moduleMetaData!;
}
/**
* Caches the resolved `fileName` in relationship to the `moduleSpecifier`
* and `containingFile` in order to reduce calls to the privileged side
* to retrieve the contents of a module.
*/
setFileName(
moduleSpecifier: ModuleSpecifier,
containingFile: ContainingFile,
fileName: ModuleFileName
): void {
this._log("setFileName", { moduleSpecifier, containingFile });
let innerMap = this._fileNamesMap.get(containingFile);
if (!innerMap) {
innerMap = new Map();
this._fileNamesMap.set(containingFile, innerMap);
}
innerMap.set(moduleSpecifier, fileName);
}
// TypeScript Language Service API
getCompilationSettings(): ts.CompilerOptions {
this._log("getCompilationSettings()");
return this._options;
}
getNewLine(): string {
return EOL;
}
getScriptFileNames(): string[] {
// This is equal to `"files"` in the `tsconfig.json`, therefore we only need
// to include the actual base source files we are evaluating at the moment,
// which would be what is set during the `.run()`
return this._scriptFileNames;
}
getScriptKind(fileName: ModuleFileName): ts.ScriptKind {
this._log("getScriptKind()", fileName);
const suffix = fileName.substr(fileName.lastIndexOf(".") + 1);
switch (suffix) {
case "ts":
return ScriptKind.TS;
case "js":
return ScriptKind.JS;
case "json":
return ScriptKind.JSON;
default:
return this._options.allowJs ? ScriptKind.JS : ScriptKind.TS;
}
}
getScriptVersion(fileName: ModuleFileName): string {
this._log("getScriptVersion()", fileName);
const moduleMetaData = this._getModuleMetaData(fileName);
return (moduleMetaData && moduleMetaData.scriptVersion) || "";
}
getScriptSnapshot(fileName: ModuleFileName): ts.IScriptSnapshot | undefined {
this._log("getScriptSnapshot()", fileName);
const moduleMetaData = this._getModuleMetaData(fileName);
if (moduleMetaData) {
return (
moduleMetaData.scriptSnapshot ||
(moduleMetaData.scriptSnapshot = {
getText(start, end) {
return moduleMetaData.sourceCode.substring(start, end);
},
getLength() {
return moduleMetaData.sourceCode.length;
},
getChangeRange() {
return undefined;
}
})
);
} else {
return undefined;
}
}
getCurrentDirectory(): string {
this._log("getCurrentDirectory()");
return "";
}
getDefaultLibFileName(): string {
this._log("getDefaultLibFileName()");
const moduleSpecifier = "lib.globals.d.ts";
const moduleMetaData = this.resolveModule(moduleSpecifier, ASSETS);
return moduleMetaData.fileName;
}
useCaseSensitiveFileNames(): boolean {
this._log("useCaseSensitiveFileNames");
return true;
}
readFile(path: string): string | undefined {
this._log("readFile", path);
return notImplemented();
}
fileExists(fileName: string): boolean {
const moduleMetaData = this._getModuleMetaData(fileName);
const exists = moduleMetaData != null;
this._log("fileExists", fileName, exists);
return exists;
}
resolveModuleNames(
moduleNames: ModuleSpecifier[],
containingFile: ContainingFile
): ts.ResolvedModule[] {
this._log("resolveModuleNames", { moduleNames, containingFile });
return moduleNames.map(name => {
let resolvedFileName;
if (name === "deno") {
resolvedFileName = this.resolveModuleName("deno.d.ts", ASSETS);
} else if (name === "compiler") {
resolvedFileName = this.resolveModuleName("compiler.d.ts", ASSETS);
} else if (name === "typescript") {
resolvedFileName = this.resolveModuleName("typescript.d.ts", ASSETS);
} else {
resolvedFileName = this.resolveModuleName(name, containingFile);
}
// According to the interface we shouldn't return `undefined` but if we
// fail to return the same length of modules to those we cannot resolve
// then TypeScript fails on an assertion that the lengths can't be
// different, so we have to return an "empty" resolved module
// TODO: all this does is push the problem downstream, and TypeScript
// will complain it can't identify the type of the file and throw
// a runtime exception, so we need to handle missing modules better
resolvedFileName = resolvedFileName || "";
// This flags to the compiler to not go looking to transpile functional
// code, anything that is in `/$asset$/` is just library code
const isExternalLibraryImport = resolvedFileName.startsWith(ASSETS);
// TODO: we should be returning a ts.ResolveModuleFull
return { resolvedFileName, isExternalLibraryImport };
});
}
// Deno specific static properties and methods
/**
* Built in modules which can be returned to external modules
*
* Placed as a private static otherwise we get use before
* declared with the `DenoCompiler`
*/
// tslint:disable-next-line:no-any
private static _builtins: { [mid: string]: any } = {
typescript: ts,
deno,
compiler: { DenoCompiler, ModuleMetaData }
};
private static _instance: DenoCompiler | undefined;
/**
* Returns the instance of `DenoCompiler` or creates a new instance.
*/
static instance(): DenoCompiler {
return (
DenoCompiler._instance || (DenoCompiler._instance = new DenoCompiler())
);
}
}

471
js/compiler_test.ts Normal file
View file

@ -0,0 +1,471 @@
// Copyright 2018 the Deno authors. All rights reserved. MIT license.
import { test, assert, assertEqual } from "./testing/testing.ts";
import * as compiler from "compiler";
import * as ts from "typescript";
// We use a silly amount of `any` in these tests...
// tslint:disable:no-any
const { DenoCompiler, ModuleMetaData } = compiler;
// Enums like this don't exist at runtime, so local copy
enum ScriptKind {
JS = 1,
TS = 3,
JSON = 6
}
interface ModuleInfo {
moduleName: string | null;
filename: string | null;
sourceCode: string | null;
outputCode: string | null;
}
const compilerInstance = DenoCompiler.instance();
// References to orignal items we are going to mock
const originals = {
_globalEval: (compilerInstance as any)._globalEval,
_log: (compilerInstance as any)._log,
_os: (compilerInstance as any)._os,
_ts: (compilerInstance as any)._ts,
_service: (compilerInstance as any)._service,
_window: (compilerInstance as any)._window
};
function mockModuleInfo(
moduleName: string | null,
filename: string | null,
sourceCode: string | null,
outputCode: string | null
): ModuleInfo {
return {
moduleName,
filename,
sourceCode,
outputCode
};
}
// Some fixtures we will us in testing
const fooBarTsSource = `import * as compiler from "compiler";
console.log(compiler);
export const foo = "bar";
`;
const fooBazTsSource = `import { foo } from "./bar.ts";
console.log(foo);
`;
// TODO(#23) Remove source map strings from fooBarTsOutput.
// tslint:disable:max-line-length
const fooBarTsOutput = `define(["require", "exports", "compiler"], function (require, exports, compiler) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
console.log(compiler);
exports.foo = "bar";
});
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYmFyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiZmlsZTovLy9yb290L3Byb2plY3QvZm9vL2Jhci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7SUFDQSxPQUFPLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBQ1QsUUFBQSxHQUFHLEdBQUcsS0FBSyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0ICogYXMgY29tcGlsZXIgZnJvbSBcImNvbXBpbGVyXCI7XG5jb25zb2xlLmxvZyhjb21waWxlcik7XG5leHBvcnQgY29uc3QgZm9vID0gXCJiYXJcIjtcbiJdfQ==
//# sourceURL=/root/project/foo/bar.ts`;
// TODO(#23) Remove source map strings from fooBazTsOutput.
const fooBazTsOutput = `define(["require", "exports", "./bar.ts"], function (require, exports, bar_ts_1) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
console.log(bar_ts_1.foo);
});
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYmF6LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiZmlsZTovLy9yb290L3Byb2plY3QvZm9vL2Jhei50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7SUFDQSxPQUFPLENBQUMsR0FBRyxDQUFDLFlBQUcsQ0FBQyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgZm9vIH0gZnJvbSBcIi4vYmFyLnRzXCI7XG5jb25zb2xlLmxvZyhmb28pO1xuIl19
//# sourceURL=/root/project/foo/baz.ts`;
// tslint:enable:max-line-length
const moduleMap: {
[containFile: string]: { [moduleSpecifier: string]: ModuleInfo };
} = {
"/root/project": {
"foo/bar.ts": mockModuleInfo(
"foo/bar",
"/root/project/foo/bar.ts",
fooBarTsSource,
null
),
"foo/baz.ts": mockModuleInfo(
"foo/baz",
"/root/project/foo/baz.ts",
fooBazTsSource,
null
),
"foo/qat.ts": mockModuleInfo(
"foo/qat",
"/root/project/foo/qat.ts",
null,
null
)
},
"/root/project/foo/baz.ts": {
"./bar.ts": mockModuleInfo(
"foo/bar",
"/root/project/foo/bar.ts",
fooBarTsSource,
fooBarTsOutput
)
}
};
const emittedFiles = {
"/root/project/foo/qat.ts": "console.log('foo');"
};
let globalEvalStack: string[] = [];
let getEmitOutputStack: string[] = [];
let logStack: any[][] = [];
let codeCacheStack: Array<{
fileName: string;
sourceCode: string;
outputCode: string;
}> = [];
let codeFetchStack: Array<{
moduleSpecifier: string;
containingFile: string;
}> = [];
function reset() {
codeFetchStack = [];
codeCacheStack = [];
logStack = [];
getEmitOutputStack = [];
globalEvalStack = [];
}
let mockDeps: string[] | undefined;
let mockFactory: compiler.AmdFactory;
function globalEvalMock(x: string): void {
globalEvalStack.push(x);
if (windowMock.define && mockDeps && mockFactory) {
windowMock.define(mockDeps, mockFactory);
}
}
function logMock(...args: any[]): void {
logStack.push(args);
}
const osMock: compiler.Os = {
codeCache(fileName: string, sourceCode: string, outputCode: string): void {
codeCacheStack.push({ fileName, sourceCode, outputCode });
},
codeFetch(moduleSpecifier: string, containingFile: string): ModuleInfo {
codeFetchStack.push({ moduleSpecifier, containingFile });
if (containingFile in moduleMap) {
if (moduleSpecifier in moduleMap[containingFile]) {
return moduleMap[containingFile][moduleSpecifier];
}
}
return mockModuleInfo(null, null, null, null);
},
exit(code: number): never {
throw new Error(`os.exit(${code})`);
}
};
const tsMock: compiler.Ts = {
createLanguageService(host: ts.LanguageServiceHost): ts.LanguageService {
return {} as ts.LanguageService;
},
formatDiagnosticsWithColorAndContext(
diagnostics: ReadonlyArray<ts.Diagnostic>,
host: ts.FormatDiagnosticsHost
): string {
return "";
}
};
const getEmitOutputPassThrough = true;
const serviceMock = {
getCompilerOptionsDiagnostics(): ts.Diagnostic[] {
return originals._service.getCompilerOptionsDiagnostics.call(
originals._service
);
},
getEmitOutput(fileName: string): ts.EmitOutput {
getEmitOutputStack.push(fileName);
if (getEmitOutputPassThrough) {
return originals._service.getEmitOutput.call(
originals._service,
fileName
);
}
if (fileName in emittedFiles) {
return {
outputFiles: [{ text: emittedFiles[fileName] }] as any,
emitSkipped: false
};
}
return { outputFiles: [], emitSkipped: false };
},
getSemanticDiagnostics(fileName: string): ts.Diagnostic[] {
return originals._service.getSemanticDiagnostics.call(
originals._service,
fileName
);
},
getSyntacticDiagnostics(fileName: string): ts.Diagnostic[] {
return originals._service.getSyntacticDiagnostics.call(
originals._service,
fileName
);
}
};
const windowMock: { define?: compiler.AmdDefine } = {};
const mocks = {
_globalEval: globalEvalMock,
_log: logMock,
_os: osMock,
_ts: tsMock,
_service: serviceMock,
_window: windowMock
};
// Setup the mocks
test(function compilerTestsSetup() {
assert("_globalEval" in compilerInstance);
assert("_log" in compilerInstance);
assert("_os" in compilerInstance);
assert("_ts" in compilerInstance);
assert("_service" in compilerInstance);
assert("_window" in compilerInstance);
Object.assign(compilerInstance, mocks);
});
test(function compilerInstance() {
assert(DenoCompiler != null);
assert(DenoCompiler.instance() != null);
});
// Testing the internal APIs
test(function compilerMakeDefine() {
const moduleMetaData = new ModuleMetaData(
"/root/project/foo/bar.ts",
fooBarTsSource,
fooBarTsOutput
);
const localDefine = compilerInstance.makeDefine(moduleMetaData);
let factoryCalled = false;
localDefine(
["require", "exports", "compiler"],
(_require, _exports, _compiler): void => {
factoryCalled = true;
assertEqual(
typeof _require,
"function",
"localRequire should be a function"
);
assert(_exports != null);
assert(
Object.keys(_exports).length === 0,
"exports should have no properties"
);
assert(compiler === _compiler, "compiler should be passed to factory");
}
);
assert(factoryCalled, "Factory expected to be called");
});
// TODO testMakeDefineExternalModule - testing that make define properly runs
// external modules, this is implicitly tested though in
// `compilerRunMultiModule`
test(function compilerRun() {
// equal to `deno foo/bar.ts`
reset();
const result = compilerInstance.run("foo/bar.ts", "/root/project");
assert(result instanceof ModuleMetaData);
assertEqual(codeFetchStack.length, 1);
assertEqual(codeCacheStack.length, 1);
assertEqual(globalEvalStack.length, 1);
const lastGlobalEval = globalEvalStack.pop();
assertEqual(lastGlobalEval, fooBarTsOutput);
const lastCodeFetch = codeFetchStack.pop();
assertEqual(lastCodeFetch, {
moduleSpecifier: "foo/bar.ts",
containingFile: "/root/project"
});
const lastCodeCache = codeCacheStack.pop();
assertEqual(lastCodeCache, {
fileName: "/root/project/foo/bar.ts",
sourceCode: fooBarTsSource,
outputCode: fooBarTsOutput
});
});
test(function compilerRunMultiModule() {
// equal to `deno foo/baz.ts`
reset();
let factoryRun = false;
mockDeps = ["require", "exports", "compiler"];
mockFactory = (...deps: any[]) => {
const [_require, _exports, _compiler] = deps;
assertEqual(typeof _require, "function");
assertEqual(typeof _exports, "object");
assertEqual(_compiler, compiler);
factoryRun = true;
Object.defineProperty(_exports, "__esModule", { value: true });
_exports.foo = "bar";
// it is too complicated to test the outer factory, because the localised
// make define already has a reference to this factory and it can't really
// be easily unwound. So we will do what we can with the inner one and
// then just clear it...
mockDeps = undefined;
mockFactory = undefined;
};
const result = compilerInstance.run("foo/baz.ts", "/root/project");
assert(result instanceof ModuleMetaData);
// we have mocked that foo/bar.ts is already cached, so two fetches,
// but only a single cache
assertEqual(codeFetchStack.length, 2);
assertEqual(codeCacheStack.length, 1);
// because of the challenges with the way the module factories are generated
// we only get one invocation of the `globalEval` mock.
assertEqual(globalEvalStack.length, 1);
assert(factoryRun);
});
// TypeScript LanguageServiceHost APIs
test(function compilerGetCompilationSettings() {
const result = compilerInstance.getCompilationSettings();
for (const key of [
"allowJs",
"module",
"outDir",
"inlineSourceMap",
"inlineSources",
"stripComments",
"target"
]) {
assert(key in result, `Expected "${key}" in compiler options.`);
}
});
test(function compilerGetNewLine() {
const result = compilerInstance.getNewLine();
assertEqual(result, "\n", "Expected newline value of '\\n'.");
});
test(function compilerGetScriptFileNames() {
compilerInstance.run("foo/bar.ts", "/root/project");
const result = compilerInstance.getScriptFileNames();
assertEqual(result.length, 1, "Expected only a single filename.");
assertEqual(result[0], "/root/project/foo/bar.ts");
});
test(function compilerGetScriptKind() {
assertEqual(compilerInstance.getScriptKind("foo.ts"), ScriptKind.TS);
assertEqual(compilerInstance.getScriptKind("foo.d.ts"), ScriptKind.TS);
assertEqual(compilerInstance.getScriptKind("foo.js"), ScriptKind.JS);
assertEqual(compilerInstance.getScriptKind("foo.json"), ScriptKind.JSON);
assertEqual(compilerInstance.getScriptKind("foo.txt"), ScriptKind.JS);
});
test(function compilerGetScriptVersion() {
const moduleMetaData = compilerInstance.resolveModule(
"foo/bar.ts",
"/root/project"
);
assertEqual(
compilerInstance.getScriptVersion(moduleMetaData.fileName),
"1",
"Expected known module to have script version of 1"
);
});
test(function compilerGetScriptVersionUnknown() {
assertEqual(
compilerInstance.getScriptVersion("/root/project/unknown_module.ts"),
"",
"Expected unknown module to have an empty script version"
);
});
test(function compilerGetScriptSnapshot() {
const moduleMetaData = compilerInstance.resolveModule(
"foo/bar.ts",
"/root/project"
);
const result = compilerInstance.getScriptSnapshot(moduleMetaData.fileName);
assert(result != null, "Expected snapshot to be defined.");
assertEqual(result.getLength(), fooBarTsSource.length);
assertEqual(
result.getText(0, 6),
"import",
"Expected .getText() to equal 'import'"
);
assertEqual(result.getChangeRange(result), undefined);
assert(!("dispose" in result));
});
test(function compilerGetCurrentDirectory() {
assertEqual(compilerInstance.getCurrentDirectory(), "");
});
test(function compilerGetDefaultLibFileName() {
assertEqual(
compilerInstance.getDefaultLibFileName(),
"$asset$/lib.globals.d.ts"
);
});
test(function compilerUseCaseSensitiveFileNames() {
assertEqual(compilerInstance.useCaseSensitiveFileNames(), true);
});
test(function compilerReadFile() {
let doesThrow = false;
try {
compilerInstance.readFile("foobar.ts");
} catch (e) {
doesThrow = true;
assert(e.message.includes("Not implemented") === true);
}
assert(doesThrow);
});
test(function compilerFileExists() {
const moduleMetaData = compilerInstance.resolveModule(
"foo/bar.ts",
"/root/project"
);
assert(compilerInstance.fileExists(moduleMetaData.fileName));
assert(compilerInstance.fileExists("$asset$/compiler.d.ts"));
assertEqual(
compilerInstance.fileExists("/root/project/unknown-module.ts"),
false
);
});
test(function compilerResolveModuleNames() {
const results = compilerInstance.resolveModuleNames(
["foo/bar.ts", "foo/baz.ts", "$asset$/lib.globals.d.ts", "deno"],
"/root/project"
);
assertEqual(results.length, 4);
const fixtures: Array<[string, boolean]> = [
["/root/project/foo/bar.ts", false],
["/root/project/foo/baz.ts", false],
["$asset$/lib.globals.d.ts", true],
["$asset$/deno.d.ts", true]
];
for (let i = 0; i < results.length; i++) {
const result = results[i];
const [resolvedFileName, isExternalLibraryImport] = fixtures[i];
assertEqual(result.resolvedFileName, resolvedFileName);
assertEqual(result.isExternalLibraryImport, isExternalLibraryImport);
}
});
// Remove the mocks
test(function compilerTestsTeardown() {
Object.assign(compilerInstance, originals);
});

View file

@ -1,6 +1,7 @@
// Copyright 2018 the Deno authors. All rights reserved. MIT license. // Copyright 2018 the Deno authors. All rights reserved. MIT license.
import { Console } from "./console"; import { Console } from "./console";
import { exit } from "./os";
import { RawSourceMap } from "./types"; import { RawSourceMap } from "./types";
import * as timers from "./timers"; import * as timers from "./timers";
import { TextEncoder, TextDecoder } from "./text_encoding"; import { TextEncoder, TextDecoder } from "./text_encoding";
@ -9,6 +10,14 @@ import * as fetch_ from "./fetch";
declare global { declare global {
interface Window { interface Window {
console: Console; console: Console;
define: Readonly<unknown>;
onerror?: (
message: string,
source: string,
lineno: number,
colno: number,
error: Error
) => void;
} }
const clearTimeout: typeof timers.clearTimer; const clearTimeout: typeof timers.clearTimer;
@ -58,6 +67,22 @@ window.clearTimeout = timers.clearTimer;
window.clearInterval = timers.clearTimer; window.clearInterval = timers.clearTimer;
window.console = new Console(libdeno.print); window.console = new Console(libdeno.print);
// Uncaught exceptions are sent to window.onerror by the privileged binding.
window.onerror = (
message: string,
source: string,
lineno: number,
colno: number,
error: 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.stack);
exit(1);
};
window.TextEncoder = TextEncoder; window.TextEncoder = TextEncoder;
window.TextDecoder = TextDecoder; window.TextDecoder = TextDecoder;

View file

@ -1,10 +1,9 @@
// Copyright 2018 the Deno authors. All rights reserved. MIT license. // Copyright 2018 the Deno authors. All rights reserved. MIT license.
import { flatbuffers } from "flatbuffers"; import { flatbuffers } from "flatbuffers";
import { deno as fbs } from "gen/msg_generated"; import { deno as fbs } from "gen/msg_generated";
import { assert, log, assignCmdId } from "./util"; import { assert, assignCmdId, log, setLogDebug } from "./util";
import * as util from "./util";
import * as os from "./os"; import * as os from "./os";
import * as runtime from "./runtime"; import { DenoCompiler } from "./compiler";
import { libdeno } from "./globals"; import { libdeno } from "./globals";
import * as timers from "./timers"; import * as timers from "./timers";
import { onFetchRes } from "./fetch"; import { onFetchRes } from "./fetch";
@ -47,7 +46,7 @@ function onMessage(ui8: Uint8Array) {
/* tslint:disable-next-line:no-default-export */ /* tslint:disable-next-line:no-default-export */
export default function denoMain() { export default function denoMain() {
libdeno.recv(onMessage); libdeno.recv(onMessage);
runtime.setup(); const compiler = DenoCompiler.instance();
// First we send an empty "Start" message to let the privlaged side know we // First we send an empty "Start" message to let the privlaged side know we
// are ready. The response should be a "StartRes" message containing the CLI // are ready. The response should be a "StartRes" message containing the CLI
@ -69,7 +68,7 @@ export default function denoMain() {
const startResMsg = new fbs.StartRes(); const startResMsg = new fbs.StartRes();
assert(base.msg(startResMsg) != null); assert(base.msg(startResMsg) != null);
util.setLogDebug(startResMsg.debugFlag()); setLogDebug(startResMsg.debugFlag());
const cwd = startResMsg.cwd(); const cwd = startResMsg.cwd();
log("cwd", cwd); log("cwd", cwd);
@ -86,8 +85,5 @@ export default function denoMain() {
os.exit(1); os.exit(1);
} }
const mod = runtime.resolveModule(inputFn, `${cwd}/`); compiler.run(inputFn, `${cwd}/`);
assert(mod != null);
// TypeScript does not track assert, therefore not null assertion
mod!.compileAndRun();
} }

View file

@ -1,376 +0,0 @@
// Copyright 2018 the Deno authors. 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 { assetSourceCode } from "./assets";
import * as os from "./os";
import * as sourceMaps from "./v8_source_maps";
import { libdeno, window, globalEval } from "./globals";
import * as deno from "./deno";
import { RawSourceMap } from "./types";
const EOL = "\n";
const ASSETS = "/$asset$/";
// 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 the privlaged binding.
window.onerror = (
message: string,
source: string,
lineno: number,
colno: number,
error: 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.stack);
os.exit(1);
};
export function setup(): void {
sourceMaps.install({
installPrepareStackTrace: true,
getGeneratedContents: (filename: string): string | RawSourceMap => {
util.log("getGeneratedContents", filename);
if (filename === "gen/bundle/main.js") {
util.assert(libdeno.mainSource.length > 0);
return libdeno.mainSource;
} else if (filename === "main.js.map") {
return libdeno.mainSourceMap;
} else if (filename === "deno_main.js") {
return "";
} else {
const mod = FileModule.load(filename);
if (!mod) {
util.log("getGeneratedContents cannot find", filename);
return "";
}
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 = "";
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 {
util.log("compileAndRun", this.sourceCode);
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);
}
execute(this.fileName, this.outputCode);
}
static load(fileName: string): FileModule | undefined {
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 && 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);
util.assert(currentModule != null);
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);
util.assert(resolved != null);
const depModule = FileModule.load(resolved!);
if (depModule) {
depModule.compileAndRun();
return depModule.exports;
}
return undefined;
}
});
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);
let filename: string | null;
let sourceCode: string | null;
let outputCode: string | null;
if (moduleSpecifier.startsWith(ASSETS) || containingFile.startsWith(ASSETS)) {
// Assets are compiled into the runtime javascript bundle.
// we _know_ `.pop()` will return a string, but TypeScript doesn't so
// not null assertion
const moduleId = moduleSpecifier.split("/").pop()!;
const assetName = moduleId.includes(".") ? moduleId : `${moduleId}.d.ts`;
util.assert(assetName in assetSourceCode, `No such asset "${assetName}"`);
sourceCode = assetSourceCode[assetName];
filename = ASSETS + assetName;
} else {
// We query Rust with a CodeFetch message. It will load the sourceCode, and
// if there is any outputCode cached, will return that as well.
const fetchResponse = os.codeFetch(moduleSpecifier, containingFile);
filename = fetchResponse.filename;
sourceCode = fetchResponse.sourceCode;
outputCode = fetchResponse.outputCode;
}
if (sourceCode == null || sourceCode.length === 0 || filename == null) {
return null;
}
util.log("resolveModule sourceCode length ", sourceCode.length);
const m = FileModule.load(filename);
if (m != null) {
return m;
} else {
// null and undefined are incompatible in strict mode, but outputCode being
// null here has no runtime behavior impact, therefore not null assertion
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 != null && outputCode.length > 0);
window["define"] = makeDefine(fileName);
outputCode += `\n//# sourceURL=${fileName}`;
globalEval(outputCode);
window["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,
inlineSources: true,
target: ts.ScriptTarget.ESNext
};
/*
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 && 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);
return util.notImplemented();
}
getNewLine() {
return EOL;
}
getCurrentDirectory() {
util.log("getCurrentDirectory");
return ".";
}
getCompilationSettings() {
util.log("getCompilationSettings");
return this.options;
}
getDefaultLibFileName(options: ts.CompilerOptions): string {
const fn = "lib.globals.d.ts"; // ts.getDefaultLibFileName(options);
util.log("getDefaultLibFileName", fn);
const m = resolveModule(fn, ASSETS);
util.assert(m != null);
// TypeScript cannot track assertions, therefore not null assertion
return m!.fileName;
}
resolveModuleNames(
moduleNames: string[],
containingFile: string
): ts.ResolvedModule[] {
//util.log("resolveModuleNames", { moduleNames, reusedNames });
return moduleNames.map(name => {
let resolvedFileName;
if (name === "deno") {
resolvedFileName = resolveModuleName("deno.d.ts", ASSETS);
} else if (name === "typescript") {
resolvedFileName = resolveModuleName("typescript.d.ts", ASSETS);
} else {
resolvedFileName = resolveModuleName(name, containingFile);
}
// According to the interface we shouldn't return `undefined` but if we
// fail to return the same length of modules to those we cannot resolve
// then TypeScript fails on an assertion that the lengths can't be
// different, so we have to return an "empty" resolved module
// TODO: all this does is push the problem downstream, and TypeScript
// will complain it can't identify the type of the file and throw
// a runtime exception, so we need to handle missing modules better
resolvedFileName = resolvedFileName || "";
// This flags to the compiler to not go looking to transpile functional
// code, anything that is in `/$asset$/` is just library code
const isExternalLibraryImport = resolvedFileName.startsWith(ASSETS);
// TODO: we should be returning a ts.ResolveModuleFull
return { resolvedFileName, isExternalLibraryImport };
});
}
}
const formatDiagnosticsHost: ts.FormatDiagnosticsHost = {
getCurrentDirectory(): string {
return ".";
},
getCanonicalFileName(fileName: string): string {
return fileName;
},
getNewLine(): string {
return EOL;
}
};

View file

@ -11,6 +11,7 @@
}, },
"files": [ "files": [
"../node_modules/typescript/lib/lib.esnext.d.ts", "../node_modules/typescript/lib/lib.esnext.d.ts",
"./compiler.ts",
"./deno.ts", "./deno.ts",
"./globals.ts" "./globals.ts"
] ]

View file

@ -6,6 +6,8 @@
import { test, assert, assertEqual } from "./testing/testing.ts"; import { test, assert, assertEqual } from "./testing/testing.ts";
import { readFileSync } from "deno"; import { readFileSync } from "deno";
import "./compiler_test.ts";
test(async function tests_test() { test(async function tests_test() {
assert(true); assert(true);
}); });

View file

@ -0,0 +1,17 @@
(async () => {
const {
returnsHi,
returnsFoo2,
printHello3
} = await import("./subdir/mod1.ts");
printHello3();
if (returnsHi() !== "Hi") {
throw Error("Unexpected");
}
if (returnsFoo2() !== "Foo") {
throw Error("Unexpected");
}
})();

View file

@ -0,0 +1 @@
Hello

View file

@ -3,8 +3,7 @@ before error
Error: error Error: error
at foo ([WILDCARD]tests/async_error.ts:4:9) at foo ([WILDCARD]tests/async_error.ts:4:9)
at eval ([WILDCARD]tests/async_error.ts:7:1) at eval ([WILDCARD]tests/async_error.ts:7:1)
at eval (<anonymous>) at DenoCompiler.eval [as _globalEval] (<anonymous>)
at execute (deno/js/runtime.ts:[WILDCARD]) at DenoCompiler.run (deno/js/compiler.ts:[WILDCARD])
at FileModule.compileAndRun (deno/js/runtime.ts:[WILDCARD])
at denoMain (deno/js/main.ts:[WILDCARD]) at denoMain (deno/js/main.ts:[WILDCARD])
at deno_main.js:1:1 at deno_main.js:1:1

View file

@ -2,8 +2,7 @@ Error: bad
at foo (file://[WILDCARD]tests/error_001.ts:2:9) at foo (file://[WILDCARD]tests/error_001.ts:2:9)
at bar (file://[WILDCARD]tests/error_001.ts:6:3) at bar (file://[WILDCARD]tests/error_001.ts:6:3)
at eval (file://[WILDCARD]tests/error_001.ts:9:1) at eval (file://[WILDCARD]tests/error_001.ts:9:1)
at eval (<anonymous>) at DenoCompiler.eval [as _globalEval] (<anonymous>)
at execute (deno/js/runtime.ts:[WILDCARD]) at DenoCompiler.run (deno/js/compiler.ts:[WILDCARD])
at FileModule.compileAndRun (deno/js/runtime.ts:[WILDCARD])
at denoMain (deno/js/main.ts:[WILDCARD]) at denoMain (deno/js/main.ts:[WILDCARD])
at deno_main.js:1:1 at deno_main.js:1:1

View file

@ -2,10 +2,9 @@ Error: exception from mod1
at Object.throwsError (file://[WILDCARD]tests/subdir/mod1.ts:16:9) at Object.throwsError (file://[WILDCARD]tests/subdir/mod1.ts:16:9)
at foo (file://[WILDCARD]tests/error_002.ts:4:3) at foo (file://[WILDCARD]tests/error_002.ts:4:3)
at eval (file://[WILDCARD]tests/error_002.ts:7:1) at eval (file://[WILDCARD]tests/error_002.ts:7:1)
at localDefine (deno/js/runtime.ts:[WILDCARD]) at localDefine (deno/js/compiler.ts:[WILDCARD])
at eval ([WILDCARD]tests/error_002.ts, <anonymous>) at eval ([WILDCARD]tests/error_002.ts, <anonymous>)
at eval (<anonymous>) at DenoCompiler.eval [as _globalEval] (<anonymous>)
at execute (deno/js/runtime.ts:[WILDCARD]) at DenoCompiler.run (deno/js/compiler.ts:[WILDCARD])
at FileModule.compileAndRun (deno/js/runtime.ts:[WILDCARD])
at denoMain (deno/js/main.ts:[WILDCARD]) at denoMain (deno/js/main.ts:[WILDCARD])
at deno_main.js:1:1 at deno_main.js:1:1

View file

@ -0,0 +1,2 @@
// console.log intentionally misspelled to trigger TypeScript error
consol.log("hello world!");

View file

@ -0,0 +1,10 @@
[WILDCARD]tests/error_003_typescript.tsILDCARD] - error TS2552: Cannot find name 'consol'. Did you mean 'console'?
[WILDCARD][0m consol.log("hello world!");
  ~~~~~~
$asset$/globals.d.tsILDCARD]
[WILDCARD][0m const console: Console;
   ~~~~~~~
'console' is declared here.

View file

@ -0,0 +1 @@
import * as badModule from "bad-module.ts";

View file

@ -0,0 +1,12 @@
Error: Cannot resolve module "bad-module.ts" from "[WILDCARD]error_004_missing_module.ts".
os.codeFetch message: [WILDCARD] (os error 2)
at throwResolutionError (deno/js/compiler.ts:[WILDCARD])
at DenoCompiler.resolveModule (deno/js/compiler.ts:[WILDCARD])
at DenoCompiler.resolveModuleName (deno/js/compiler.ts:[WILDCARD])
at moduleNames.map.name (deno/js/compiler.ts:[WILDCARD])
at Array.map (<anonymous>)
at DenoCompiler.resolveModuleNames (deno/js/compiler.ts:[WILDCARD])
at Object.compilerHost.resolveModuleNames (deno/third_party/node_modules/typescript/lib/typescript.js:[WILDCARD])
at resolveModuleNamesWorker (deno/third_party/node_modules/typescript/lib/typescript.js:[WILDCARD])
at resolveModuleNamesReusingOldState (deno/third_party/node_modules/typescript/lib/typescript.js:[WILDCARD])
at processImportedModules (deno/third_party/node_modules/typescript/lib/typescript.js:[WILDCARD])