mirror of
https://github.com/denoland/deno.git
synced 2024-12-25 16:49:18 -05:00
parent
c5bb412933
commit
e7cab71574
18 changed files with 1154 additions and 396 deletions
4
BUILD.gn
4
BUILD.gn
|
@ -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",
|
||||||
|
|
|
@ -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
595
js/compiler.ts
Normal 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
471
js/compiler_test.ts
Normal 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);
|
||||||
|
});
|
|
@ -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;
|
||||||
|
|
||||||
|
|
14
js/main.ts
14
js/main.ts
|
@ -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();
|
|
||||||
}
|
}
|
||||||
|
|
376
js/runtime.ts
376
js/runtime.ts
|
@ -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;
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -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"
|
||||||
]
|
]
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
17
tests/013_dynamic_import.ts
Normal file
17
tests/013_dynamic_import.ts
Normal 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");
|
||||||
|
}
|
||||||
|
})();
|
1
tests/013_dynamic_import.ts.out
Normal file
1
tests/013_dynamic_import.ts.out
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Hello
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
2
tests/error_003_typescript.ts
Normal file
2
tests/error_003_typescript.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
// console.log intentionally misspelled to trigger TypeScript error
|
||||||
|
consol.log("hello world!");
|
10
tests/error_003_typescript.ts.out
Normal file
10
tests/error_003_typescript.ts.out
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
[96m[WILDCARD]tests/error_003_typescript.ts[WILDCARD] - [91merror[0m[90m TS2552: [0mCannot find name 'consol'. Did you mean 'console'?
|
||||||
|
|
||||||
|
[30;47m[WILDCARD][0m consol.log("hello world!");
|
||||||
|
[30;47m [0m [91m~~~~~~[0m
|
||||||
|
|
||||||
|
[96m$asset$/globals.d.ts[WILDCARD]
|
||||||
|
[30;47m[WILDCARD][0m const console: Console;
|
||||||
|
[30;47m [0m [96m ~~~~~~~[0m
|
||||||
|
'console' is declared here.
|
||||||
|
|
1
tests/error_004_missing_module.ts
Normal file
1
tests/error_004_missing_module.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
import * as badModule from "bad-module.ts";
|
12
tests/error_004_missing_module.ts.out
Normal file
12
tests/error_004_missing_module.ts.out
Normal 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])
|
Loading…
Reference in a new issue