summaryrefslogtreecommitdiff
path: root/cli/tsc/99_main_compiler.js
diff options
context:
space:
mode:
Diffstat (limited to 'cli/tsc/99_main_compiler.js')
-rw-r--r--cli/tsc/99_main_compiler.js1867
1 files changed, 1867 insertions, 0 deletions
diff --git a/cli/tsc/99_main_compiler.js b/cli/tsc/99_main_compiler.js
new file mode 100644
index 000000000..d64896862
--- /dev/null
+++ b/cli/tsc/99_main_compiler.js
@@ -0,0 +1,1867 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+// This module is the entry point for "compiler" isolate, ie. the one
+// that is created when Deno needs to compile TS/WASM to JS.
+//
+// It provides a single functions that should be called by Rust:
+// - `bootstrapTsCompilerRuntime`
+// This functions must be called when creating isolate
+// to properly setup runtime.
+
+// Removes the `__proto__` for security reasons. This intentionally makes
+// Deno non compliant with ECMA-262 Annex B.2.2.1
+//
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+delete Object.prototype.__proto__;
+
+((window) => {
+ const core = Deno.core;
+ const { bold, cyan, yellow } = window.__bootstrap.colors;
+ const { assert, log, notImplemented } = window.__bootstrap.util;
+ const { DiagnosticCategory } = window.__bootstrap.diagnostics;
+
+ const unstableDenoGlobalProperties = [
+ "umask",
+ "linkSync",
+ "link",
+ "symlinkSync",
+ "symlink",
+ "loadavg",
+ "osRelease",
+ "openPlugin",
+ "DiagnosticCategory",
+ "DiagnosticMessageChain",
+ "DiagnosticItem",
+ "Diagnostic",
+ "formatDiagnostics",
+ "CompilerOptions",
+ "TranspileOnlyResult",
+ "transpileOnly",
+ "compile",
+ "bundle",
+ "Location",
+ "applySourceMap",
+ "LinuxSignal",
+ "MacOSSignal",
+ "Signal",
+ "SignalStream",
+ "signal",
+ "signals",
+ "setRaw",
+ "utimeSync",
+ "utime",
+ "ShutdownMode",
+ "shutdown",
+ "DatagramConn",
+ "UnixListenOptions",
+ "listen",
+ "listenDatagram",
+ "UnixConnectOptions",
+ "connect",
+ "StartTlsOptions",
+ "startTls",
+ "kill",
+ "PermissionName",
+ "PermissionState",
+ "RunPermissionDescriptor",
+ "ReadPermissionDescriptor",
+ "WritePermissionDescriptor",
+ "NetPermissionDescriptor",
+ "EnvPermissionDescriptor",
+ "PluginPermissionDescriptor",
+ "HrtimePermissionDescriptor",
+ "PermissionDescriptor",
+ "Permissions",
+ "PermissionStatus",
+ "hostname",
+ "ppid",
+ ];
+
+ function transformMessageText(messageText, code) {
+ switch (code) {
+ case 2339: {
+ const property = messageText
+ .replace(/^Property '/, "")
+ .replace(/' does not exist on type 'typeof Deno'\./, "");
+
+ if (
+ messageText.endsWith("on type 'typeof Deno'.") &&
+ unstableDenoGlobalProperties.includes(property)
+ ) {
+ return `${messageText} 'Deno.${property}' is an unstable API. Did you forget to run with the '--unstable' flag?`;
+ }
+ break;
+ }
+ case 2551: {
+ const suggestionMessagePattern = / Did you mean '(.+)'\?$/;
+ const property = messageText
+ .replace(/^Property '/, "")
+ .replace(/' does not exist on type 'typeof Deno'\./, "")
+ .replace(suggestionMessagePattern, "");
+ const suggestion = messageText.match(suggestionMessagePattern);
+ const replacedMessageText = messageText.replace(
+ suggestionMessagePattern,
+ "",
+ );
+ if (suggestion && unstableDenoGlobalProperties.includes(property)) {
+ const suggestedProperty = suggestion[1];
+ return `${replacedMessageText} 'Deno.${property}' is an unstable API. Did you forget to run with the '--unstable' flag, or did you mean '${suggestedProperty}'?`;
+ }
+ break;
+ }
+ }
+
+ return messageText;
+ }
+
+ function fromDiagnosticCategory(
+ category,
+ ) {
+ switch (category) {
+ case ts.DiagnosticCategory.Error:
+ return DiagnosticCategory.Error;
+ case ts.DiagnosticCategory.Message:
+ return DiagnosticCategory.Info;
+ case ts.DiagnosticCategory.Suggestion:
+ return DiagnosticCategory.Suggestion;
+ case ts.DiagnosticCategory.Warning:
+ return DiagnosticCategory.Warning;
+ default:
+ throw new Error(
+ `Unexpected DiagnosticCategory: "${category}"/"${
+ ts.DiagnosticCategory[category]
+ }"`,
+ );
+ }
+ }
+
+ function getSourceInformation(
+ sourceFile,
+ start,
+ length,
+ ) {
+ const scriptResourceName = sourceFile.fileName;
+ const {
+ line: lineNumber,
+ character: startColumn,
+ } = sourceFile.getLineAndCharacterOfPosition(start);
+ const endPosition = sourceFile.getLineAndCharacterOfPosition(
+ start + length,
+ );
+ const endColumn = lineNumber === endPosition.line
+ ? endPosition.character
+ : startColumn;
+ const lastLineInFile = sourceFile.getLineAndCharacterOfPosition(
+ sourceFile.text.length,
+ ).line;
+ const lineStart = sourceFile.getPositionOfLineAndCharacter(lineNumber, 0);
+ const lineEnd = lineNumber < lastLineInFile
+ ? sourceFile.getPositionOfLineAndCharacter(lineNumber + 1, 0)
+ : sourceFile.text.length;
+ const sourceLine = sourceFile.text
+ .slice(lineStart, lineEnd)
+ .replace(/\s+$/g, "")
+ .replace("\t", " ");
+ return {
+ sourceLine,
+ lineNumber,
+ scriptResourceName,
+ startColumn,
+ endColumn,
+ };
+ }
+
+ function fromDiagnosticMessageChain(
+ messageChain,
+ ) {
+ if (!messageChain) {
+ return undefined;
+ }
+
+ return messageChain.map(({ messageText, code, category, next }) => {
+ const message = transformMessageText(messageText, code);
+ return {
+ message,
+ code,
+ category: fromDiagnosticCategory(category),
+ next: fromDiagnosticMessageChain(next),
+ };
+ });
+ }
+
+ function parseDiagnostic(
+ item,
+ ) {
+ const {
+ messageText,
+ category: sourceCategory,
+ code,
+ file,
+ start: startPosition,
+ length,
+ } = item;
+ const sourceInfo = file && startPosition && length
+ ? getSourceInformation(file, startPosition, length)
+ : undefined;
+ const endPosition = startPosition && length
+ ? startPosition + length
+ : undefined;
+ const category = fromDiagnosticCategory(sourceCategory);
+
+ let message;
+ let messageChain;
+ if (typeof messageText === "string") {
+ message = transformMessageText(messageText, code);
+ } else {
+ message = transformMessageText(messageText.messageText, messageText.code);
+ messageChain = fromDiagnosticMessageChain([messageText])[0];
+ }
+
+ const base = {
+ message,
+ messageChain,
+ code,
+ category,
+ startPosition,
+ endPosition,
+ };
+
+ return sourceInfo ? { ...base, ...sourceInfo } : base;
+ }
+
+ function parseRelatedInformation(
+ relatedInformation,
+ ) {
+ const result = [];
+ for (const item of relatedInformation) {
+ result.push(parseDiagnostic(item));
+ }
+ return result;
+ }
+
+ function fromTypeScriptDiagnostic(
+ diagnostics,
+ ) {
+ const items = [];
+ for (const sourceDiagnostic of diagnostics) {
+ const item = parseDiagnostic(sourceDiagnostic);
+ if (sourceDiagnostic.relatedInformation) {
+ item.relatedInformation = parseRelatedInformation(
+ sourceDiagnostic.relatedInformation,
+ );
+ }
+ items.push(item);
+ }
+ return { items };
+ }
+
+ // We really don't want to depend on JSON dispatch during snapshotting, so
+ // this op exchanges strings with Rust as raw byte arrays.
+ function getAsset(name) {
+ const opId = core.ops()["op_fetch_asset"];
+ const sourceCodeBytes = core.dispatch(opId, core.encode(name));
+ return core.decode(sourceCodeBytes);
+ }
+
+ // Constants used by `normalizeString` and `resolvePath`
+ const CHAR_DOT = 46; /* . */
+ const CHAR_FORWARD_SLASH = 47; /* / */
+ // Using incremental compile APIs requires that all
+ // paths must be either relative or absolute. Since
+ // analysis in Rust operates on fully resolved URLs,
+ // it makes sense to use the same scheme here.
+ const ASSETS = "asset://";
+ const OUT_DIR = "deno://";
+ // This constant is passed to compiler settings when
+ // doing incremental compiles. Contents of this
+ // file are passed back to Rust and saved to $DENO_DIR.
+ const TS_BUILD_INFO = "cache:///tsbuildinfo.json";
+
+ // TODO(Bartlomieju): this check should be done in Rust
+ const IGNORED_COMPILER_OPTIONS = [
+ "allowSyntheticDefaultImports",
+ "allowUmdGlobalAccess",
+ "assumeChangesOnlyAffectDirectDependencies",
+ "baseUrl",
+ "build",
+ "composite",
+ "declaration",
+ "declarationDir",
+ "declarationMap",
+ "diagnostics",
+ "downlevelIteration",
+ "emitBOM",
+ "emitDeclarationOnly",
+ "esModuleInterop",
+ "extendedDiagnostics",
+ "forceConsistentCasingInFileNames",
+ "generateCpuProfile",
+ "help",
+ "importHelpers",
+ "incremental",
+ "inlineSourceMap",
+ "inlineSources",
+ "init",
+ "listEmittedFiles",
+ "listFiles",
+ "mapRoot",
+ "maxNodeModuleJsDepth",
+ "module",
+ "moduleResolution",
+ "newLine",
+ "noEmit",
+ "noEmitHelpers",
+ "noEmitOnError",
+ "noLib",
+ "noResolve",
+ "out",
+ "outDir",
+ "outFile",
+ "paths",
+ "preserveSymlinks",
+ "preserveWatchOutput",
+ "pretty",
+ "rootDir",
+ "rootDirs",
+ "showConfig",
+ "skipDefaultLibCheck",
+ "skipLibCheck",
+ "sourceMap",
+ "sourceRoot",
+ "stripInternal",
+ "target",
+ "traceResolution",
+ "tsBuildInfoFile",
+ "types",
+ "typeRoots",
+ "version",
+ "watch",
+ ];
+
+ const DEFAULT_BUNDLER_OPTIONS = {
+ allowJs: true,
+ inlineSourceMap: false,
+ module: ts.ModuleKind.System,
+ outDir: undefined,
+ outFile: `${OUT_DIR}/bundle.js`,
+ // disabled until we have effective way to modify source maps
+ sourceMap: false,
+ };
+
+ const DEFAULT_INCREMENTAL_COMPILE_OPTIONS = {
+ allowJs: false,
+ allowNonTsExtensions: true,
+ checkJs: false,
+ esModuleInterop: true,
+ incremental: true,
+ inlineSourceMap: true,
+ jsx: ts.JsxEmit.React,
+ module: ts.ModuleKind.ESNext,
+ outDir: OUT_DIR,
+ resolveJsonModule: true,
+ sourceMap: false,
+ strict: true,
+ stripComments: true,
+ target: ts.ScriptTarget.ESNext,
+ tsBuildInfoFile: TS_BUILD_INFO,
+ };
+
+ const DEFAULT_COMPILE_OPTIONS = {
+ allowJs: false,
+ allowNonTsExtensions: true,
+ checkJs: false,
+ esModuleInterop: true,
+ jsx: ts.JsxEmit.React,
+ module: ts.ModuleKind.ESNext,
+ outDir: OUT_DIR,
+ sourceMap: true,
+ strict: true,
+ removeComments: true,
+ target: ts.ScriptTarget.ESNext,
+ };
+
+ const DEFAULT_TRANSPILE_OPTIONS = {
+ esModuleInterop: true,
+ inlineSourceMap: true,
+ jsx: ts.JsxEmit.React,
+ module: ts.ModuleKind.ESNext,
+ removeComments: true,
+ target: ts.ScriptTarget.ESNext,
+ };
+
+ const DEFAULT_RUNTIME_COMPILE_OPTIONS = {
+ outDir: undefined,
+ };
+
+ const DEFAULT_RUNTIME_TRANSPILE_OPTIONS = {
+ esModuleInterop: true,
+ module: ts.ModuleKind.ESNext,
+ sourceMap: true,
+ scriptComments: true,
+ target: ts.ScriptTarget.ESNext,
+ };
+
+ const CompilerHostTarget = {
+ Main: "main",
+ Runtime: "runtime",
+ Worker: "worker",
+ };
+
+ // Warning! The values in this enum are duplicated in `cli/msg.rs`
+ // Update carefully!
+ const MediaType = {
+ 0: "JavaScript",
+ 1: "JSX",
+ 2: "TypeScript",
+ 3: "TSX",
+ 4: "Json",
+ 5: "Wasm",
+ 6: "Unknown",
+ JavaScript: 0,
+ JSX: 1,
+ TypeScript: 2,
+ TSX: 3,
+ Json: 4,
+ Wasm: 5,
+ Unknown: 6,
+ };
+
+ function getExtension(fileName, mediaType) {
+ switch (mediaType) {
+ case MediaType.JavaScript:
+ return ts.Extension.Js;
+ case MediaType.JSX:
+ return ts.Extension.Jsx;
+ case MediaType.TypeScript:
+ return fileName.endsWith(".d.ts") ? ts.Extension.Dts : ts.Extension.Ts;
+ case MediaType.TSX:
+ return ts.Extension.Tsx;
+ case MediaType.Wasm:
+ // Custom marker for Wasm type.
+ return ts.Extension.Js;
+ case MediaType.Unknown:
+ default:
+ throw TypeError(
+ `Cannot resolve extension for "${fileName}" with mediaType "${
+ MediaType[mediaType]
+ }".`,
+ );
+ }
+ }
+
+ /** A global cache of module source files that have been loaded.
+ * This cache will be rewritten to be populated on compiler startup
+ * with files provided from Rust in request message.
+ */
+ const SOURCE_FILE_CACHE = new Map();
+ /** A map of maps which cache resolved specifier for each import in a file.
+ * This cache is used so `resolveModuleNames` ops is called as few times
+ * as possible.
+ *
+ * First map's key is "referrer" URL ("file://a/b/c/mod.ts")
+ * Second map's key is "raw" import specifier ("./foo.ts")
+ * Second map's value is resolved import URL ("file:///a/b/c/foo.ts")
+ */
+ const RESOLVED_SPECIFIER_CACHE = new Map();
+
+ function configure(
+ defaultOptions,
+ source,
+ path,
+ cwd,
+ ) {
+ const { config, error } = ts.parseConfigFileTextToJson(path, source);
+ if (error) {
+ return { diagnostics: [error], options: defaultOptions };
+ }
+ const { options, errors } = ts.convertCompilerOptionsFromJson(
+ config.compilerOptions,
+ cwd,
+ );
+ const ignoredOptions = [];
+ for (const key of Object.keys(options)) {
+ if (
+ IGNORED_COMPILER_OPTIONS.includes(key) &&
+ (!(key in defaultOptions) || options[key] !== defaultOptions[key])
+ ) {
+ ignoredOptions.push(key);
+ delete options[key];
+ }
+ }
+ return {
+ options: Object.assign({}, defaultOptions, options),
+ ignoredOptions: ignoredOptions.length ? ignoredOptions : undefined,
+ diagnostics: errors.length ? errors : undefined,
+ };
+ }
+
+ class SourceFile {
+ constructor(json) {
+ this.processed = false;
+ Object.assign(this, json);
+ this.extension = getExtension(this.url, this.mediaType);
+ }
+
+ static addToCache(json) {
+ if (SOURCE_FILE_CACHE.has(json.url)) {
+ throw new TypeError("SourceFile already exists");
+ }
+ const sf = new SourceFile(json);
+ SOURCE_FILE_CACHE.set(sf.url, sf);
+ return sf;
+ }
+
+ static getCached(url) {
+ return SOURCE_FILE_CACHE.get(url);
+ }
+
+ static cacheResolvedUrl(
+ resolvedUrl,
+ rawModuleSpecifier,
+ containingFile,
+ ) {
+ containingFile = containingFile || "";
+ let innerCache = RESOLVED_SPECIFIER_CACHE.get(containingFile);
+ if (!innerCache) {
+ innerCache = new Map();
+ RESOLVED_SPECIFIER_CACHE.set(containingFile, innerCache);
+ }
+ innerCache.set(rawModuleSpecifier, resolvedUrl);
+ }
+
+ static getResolvedUrl(
+ moduleSpecifier,
+ containingFile,
+ ) {
+ const containingCache = RESOLVED_SPECIFIER_CACHE.get(containingFile);
+ if (containingCache) {
+ return containingCache.get(moduleSpecifier);
+ }
+ return undefined;
+ }
+ }
+
+ function getAssetInternal(filename) {
+ const lastSegment = filename.split("/").pop();
+ const url = ts.libMap.has(lastSegment)
+ ? ts.libMap.get(lastSegment)
+ : lastSegment;
+ const sourceFile = SourceFile.getCached(url);
+ if (sourceFile) {
+ return sourceFile;
+ }
+ const name = url.includes(".") ? url : `${url}.d.ts`;
+ const sourceCode = getAsset(name);
+ return SourceFile.addToCache({
+ url,
+ filename: `${ASSETS}/${name}`,
+ mediaType: MediaType.TypeScript,
+ versionHash: "1",
+ sourceCode,
+ });
+ }
+
+ class Host {
+ #options = DEFAULT_COMPILE_OPTIONS;
+ #target = "";
+ #writeFile = null;
+ /* Deno specific APIs */
+
+ constructor({
+ bundle = false,
+ incremental = false,
+ target,
+ unstable,
+ writeFile,
+ }) {
+ this.#target = target;
+ this.#writeFile = writeFile;
+ if (bundle) {
+ // options we need to change when we are generating a bundle
+ Object.assign(this.#options, DEFAULT_BUNDLER_OPTIONS);
+ } else if (incremental) {
+ Object.assign(this.#options, DEFAULT_INCREMENTAL_COMPILE_OPTIONS);
+ }
+ if (unstable) {
+ this.#options.lib = [
+ target === CompilerHostTarget.Worker
+ ? "lib.deno.worker.d.ts"
+ : "lib.deno.window.d.ts",
+ "lib.deno.unstable.d.ts",
+ ];
+ }
+ }
+
+ get options() {
+ return this.#options;
+ }
+
+ configure(
+ cwd,
+ path,
+ configurationText,
+ ) {
+ log("compiler::host.configure", path);
+ const { options, ...result } = configure(
+ this.#options,
+ configurationText,
+ path,
+ cwd,
+ );
+ this.#options = options;
+ return result;
+ }
+
+ mergeOptions(...options) {
+ Object.assign(this.#options, ...options);
+ return Object.assign({}, this.#options);
+ }
+
+ /* TypeScript CompilerHost APIs */
+
+ fileExists(_fileName) {
+ return notImplemented();
+ }
+
+ getCanonicalFileName(fileName) {
+ return fileName;
+ }
+
+ getCompilationSettings() {
+ log("compiler::host.getCompilationSettings()");
+ return this.#options;
+ }
+
+ getCurrentDirectory() {
+ return "";
+ }
+
+ getDefaultLibFileName(_options) {
+ log("compiler::host.getDefaultLibFileName()");
+ switch (this.#target) {
+ case CompilerHostTarget.Main:
+ case CompilerHostTarget.Runtime:
+ return `${ASSETS}/lib.deno.window.d.ts`;
+ case CompilerHostTarget.Worker:
+ return `${ASSETS}/lib.deno.worker.d.ts`;
+ }
+ }
+
+ getNewLine() {
+ return "\n";
+ }
+
+ getSourceFile(
+ fileName,
+ languageVersion,
+ onError,
+ shouldCreateNewSourceFile,
+ ) {
+ log("compiler::host.getSourceFile", fileName);
+ try {
+ assert(!shouldCreateNewSourceFile);
+ const sourceFile = fileName.startsWith(ASSETS)
+ ? getAssetInternal(fileName)
+ : SourceFile.getCached(fileName);
+ assert(sourceFile != null);
+ if (!sourceFile.tsSourceFile) {
+ assert(sourceFile.sourceCode != null);
+ const tsSourceFileName = fileName.startsWith(ASSETS)
+ ? sourceFile.filename
+ : fileName;
+
+ sourceFile.tsSourceFile = ts.createSourceFile(
+ tsSourceFileName,
+ sourceFile.sourceCode,
+ languageVersion,
+ );
+ sourceFile.tsSourceFile.version = sourceFile.versionHash;
+ delete sourceFile.sourceCode;
+ }
+ return sourceFile.tsSourceFile;
+ } catch (e) {
+ if (onError) {
+ onError(String(e));
+ } else {
+ throw e;
+ }
+ return undefined;
+ }
+ }
+
+ readFile(_fileName) {
+ return notImplemented();
+ }
+
+ resolveModuleNames(
+ moduleNames,
+ containingFile,
+ ) {
+ log("compiler::host.resolveModuleNames", {
+ moduleNames,
+ containingFile,
+ });
+ const resolved = moduleNames.map((specifier) => {
+ const maybeUrl = SourceFile.getResolvedUrl(specifier, containingFile);
+
+ log("compiler::host.resolveModuleNames maybeUrl", {
+ specifier,
+ maybeUrl,
+ });
+
+ let sourceFile = undefined;
+
+ if (specifier.startsWith(ASSETS)) {
+ sourceFile = getAssetInternal(specifier);
+ } else if (typeof maybeUrl !== "undefined") {
+ sourceFile = SourceFile.getCached(maybeUrl);
+ }
+
+ if (!sourceFile) {
+ return undefined;
+ }
+
+ return {
+ resolvedFileName: sourceFile.url,
+ isExternalLibraryImport: specifier.startsWith(ASSETS),
+ extension: sourceFile.extension,
+ };
+ });
+ log(resolved);
+ return resolved;
+ }
+
+ useCaseSensitiveFileNames() {
+ return true;
+ }
+
+ writeFile(
+ fileName,
+ data,
+ _writeByteOrderMark,
+ _onError,
+ sourceFiles,
+ ) {
+ log("compiler::host.writeFile", fileName);
+ this.#writeFile(fileName, data, sourceFiles);
+ }
+ }
+
+ class IncrementalCompileHost extends Host {
+ #buildInfo = "";
+
+ constructor(options) {
+ super({ ...options, incremental: true });
+ const { buildInfo } = options;
+ if (buildInfo) {
+ this.#buildInfo = buildInfo;
+ }
+ }
+
+ readFile(fileName) {
+ if (fileName == TS_BUILD_INFO) {
+ return this.#buildInfo;
+ }
+ throw new Error("unreachable");
+ }
+ }
+
+ // NOTE: target doesn't really matter here,
+ // this is in fact a mock host created just to
+ // load all type definitions and snapshot them.
+ let SNAPSHOT_HOST = new Host({
+ target: CompilerHostTarget.Main,
+ writeFile() {},
+ });
+ const SNAPSHOT_COMPILER_OPTIONS = SNAPSHOT_HOST.getCompilationSettings();
+
+ // This is a hacky way of adding our libs to the libs available in TypeScript()
+ // as these are internal APIs of TypeScript which maintain valid libs
+ ts.libs.push("deno.ns", "deno.window", "deno.worker", "deno.shared_globals");
+ ts.libMap.set("deno.ns", "lib.deno.ns.d.ts");
+ ts.libMap.set("deno.window", "lib.deno.window.d.ts");
+ ts.libMap.set("deno.worker", "lib.deno.worker.d.ts");
+ ts.libMap.set("deno.shared_globals", "lib.deno.shared_globals.d.ts");
+ ts.libMap.set("deno.unstable", "lib.deno.unstable.d.ts");
+
+ // this pre-populates the cache at snapshot time of our library files, so they
+ // are available in the future when needed.
+ SNAPSHOT_HOST.getSourceFile(
+ `${ASSETS}/lib.deno.ns.d.ts`,
+ ts.ScriptTarget.ESNext,
+ );
+ SNAPSHOT_HOST.getSourceFile(
+ `${ASSETS}/lib.deno.window.d.ts`,
+ ts.ScriptTarget.ESNext,
+ );
+ SNAPSHOT_HOST.getSourceFile(
+ `${ASSETS}/lib.deno.worker.d.ts`,
+ ts.ScriptTarget.ESNext,
+ );
+ SNAPSHOT_HOST.getSourceFile(
+ `${ASSETS}/lib.deno.shared_globals.d.ts`,
+ ts.ScriptTarget.ESNext,
+ );
+ SNAPSHOT_HOST.getSourceFile(
+ `${ASSETS}/lib.deno.unstable.d.ts`,
+ ts.ScriptTarget.ESNext,
+ );
+
+ // We never use this program; it's only created
+ // during snapshotting to hydrate and populate
+ // source file cache with lib declaration files.
+ const _TS_SNAPSHOT_PROGRAM = ts.createProgram({
+ rootNames: [`${ASSETS}/bootstrap.ts`],
+ options: SNAPSHOT_COMPILER_OPTIONS,
+ host: SNAPSHOT_HOST,
+ });
+
+ // Derference the snapshot host so it can be GCed
+ SNAPSHOT_HOST = undefined;
+
+ // This function is called only during snapshotting process
+ const SYSTEM_LOADER = getAsset("system_loader.js");
+ const SYSTEM_LOADER_ES5 = getAsset("system_loader_es5.js");
+
+ function buildLocalSourceFileCache(
+ sourceFileMap,
+ ) {
+ for (const entry of Object.values(sourceFileMap)) {
+ assert(entry.sourceCode.length > 0);
+ SourceFile.addToCache({
+ url: entry.url,
+ filename: entry.url,
+ mediaType: entry.mediaType,
+ sourceCode: entry.sourceCode,
+ versionHash: entry.versionHash,
+ });
+
+ for (const importDesc of entry.imports) {
+ let mappedUrl = importDesc.resolvedSpecifier;
+ const importedFile = sourceFileMap[importDesc.resolvedSpecifier];
+ assert(importedFile);
+ const isJsOrJsx = importedFile.mediaType === MediaType.JavaScript ||
+ importedFile.mediaType === MediaType.JSX;
+ // If JS or JSX perform substitution for types if available
+ if (isJsOrJsx) {
+ if (importedFile.typeHeaders.length > 0) {
+ const typeHeaders = importedFile.typeHeaders[0];
+ mappedUrl = typeHeaders.resolvedSpecifier;
+ } else if (importDesc.resolvedTypeDirective) {
+ mappedUrl = importDesc.resolvedTypeDirective;
+ } else if (importedFile.typesDirectives.length > 0) {
+ const typeDirective = importedFile.typesDirectives[0];
+ mappedUrl = typeDirective.resolvedSpecifier;
+ }
+ }
+
+ mappedUrl = mappedUrl.replace("memory://", "");
+ SourceFile.cacheResolvedUrl(mappedUrl, importDesc.specifier, entry.url);
+ }
+ for (const fileRef of entry.referencedFiles) {
+ SourceFile.cacheResolvedUrl(
+ fileRef.resolvedSpecifier.replace("memory://", ""),
+ fileRef.specifier,
+ entry.url,
+ );
+ }
+ for (const fileRef of entry.libDirectives) {
+ SourceFile.cacheResolvedUrl(
+ fileRef.resolvedSpecifier.replace("memory://", ""),
+ fileRef.specifier,
+ entry.url,
+ );
+ }
+ }
+ }
+
+ function buildSourceFileCache(
+ sourceFileMap,
+ ) {
+ for (const entry of Object.values(sourceFileMap)) {
+ SourceFile.addToCache({
+ url: entry.url,
+ filename: entry.url,
+ mediaType: entry.mediaType,
+ sourceCode: entry.sourceCode,
+ versionHash: entry.versionHash,
+ });
+
+ for (const importDesc of entry.imports) {
+ let mappedUrl = importDesc.resolvedSpecifier;
+ const importedFile = sourceFileMap[importDesc.resolvedSpecifier];
+ // IMPORTANT: due to HTTP redirects we might end up in situation
+ // where URL points to a file with completely different URL.
+ // In that case we take value of `redirect` field and cache
+ // resolved specifier pointing to the value of the redirect.
+ // It's not very elegant solution and should be rethinked.
+ assert(importedFile);
+ if (importedFile.redirect) {
+ mappedUrl = importedFile.redirect;
+ }
+ const isJsOrJsx = importedFile.mediaType === MediaType.JavaScript ||
+ importedFile.mediaType === MediaType.JSX;
+ // If JS or JSX perform substitution for types if available
+ if (isJsOrJsx) {
+ if (importedFile.typeHeaders.length > 0) {
+ const typeHeaders = importedFile.typeHeaders[0];
+ mappedUrl = typeHeaders.resolvedSpecifier;
+ } else if (importDesc.resolvedTypeDirective) {
+ mappedUrl = importDesc.resolvedTypeDirective;
+ } else if (importedFile.typesDirectives.length > 0) {
+ const typeDirective = importedFile.typesDirectives[0];
+ mappedUrl = typeDirective.resolvedSpecifier;
+ }
+ }
+
+ SourceFile.cacheResolvedUrl(mappedUrl, importDesc.specifier, entry.url);
+ }
+ for (const fileRef of entry.referencedFiles) {
+ SourceFile.cacheResolvedUrl(
+ fileRef.resolvedSpecifier,
+ fileRef.specifier,
+ entry.url,
+ );
+ }
+ for (const fileRef of entry.libDirectives) {
+ SourceFile.cacheResolvedUrl(
+ fileRef.resolvedSpecifier,
+ fileRef.specifier,
+ entry.url,
+ );
+ }
+ }
+ }
+
+ // Warning! The values in this enum are duplicated in `cli/msg.rs`
+ // Update carefully!
+ const CompilerRequestType = {
+ Compile: 0,
+ Transpile: 1,
+ Bundle: 2,
+ RuntimeCompile: 3,
+ RuntimeBundle: 4,
+ RuntimeTranspile: 5,
+ };
+
+ function createBundleWriteFile(state) {
+ return function writeFile(
+ _fileName,
+ data,
+ sourceFiles,
+ ) {
+ assert(sourceFiles != null);
+ assert(state.host);
+ // we only support single root names for bundles
+ assert(state.rootNames.length === 1);
+ state.bundleOutput = buildBundle(
+ state.rootNames[0],
+ data,
+ sourceFiles,
+ state.host.options.target ?? ts.ScriptTarget.ESNext,
+ );
+ };
+ }
+
+ function createCompileWriteFile(
+ state,
+ ) {
+ return function writeFile(
+ fileName,
+ data,
+ sourceFiles,
+ ) {
+ const isBuildInfo = fileName === TS_BUILD_INFO;
+
+ if (isBuildInfo) {
+ assert(isBuildInfo);
+ state.buildInfo = data;
+ return;
+ }
+
+ assert(sourceFiles);
+ assert(sourceFiles.length === 1);
+ state.emitMap[fileName] = {
+ filename: sourceFiles[0].fileName,
+ contents: data,
+ };
+ };
+ }
+
+ function createRuntimeCompileWriteFile(
+ state,
+ ) {
+ return function writeFile(
+ fileName,
+ data,
+ sourceFiles,
+ ) {
+ assert(sourceFiles);
+ assert(sourceFiles.length === 1);
+ state.emitMap[fileName] = {
+ filename: sourceFiles[0].fileName,
+ contents: data,
+ };
+ };
+ }
+
+ function convertCompilerOptions(str) {
+ const options = JSON.parse(str);
+ const out = {};
+ const keys = Object.keys(options);
+ const files = [];
+ for (const key of keys) {
+ switch (key) {
+ case "jsx":
+ const value = options[key];
+ if (value === "preserve") {
+ out[key] = ts.JsxEmit.Preserve;
+ } else if (value === "react") {
+ out[key] = ts.JsxEmit.React;
+ } else {
+ out[key] = ts.JsxEmit.ReactNative;
+ }
+ break;
+ case "module":
+ switch (options[key]) {
+ case "amd":
+ out[key] = ts.ModuleKind.AMD;
+ break;
+ case "commonjs":
+ out[key] = ts.ModuleKind.CommonJS;
+ break;
+ case "es2015":
+ case "es6":
+ out[key] = ts.ModuleKind.ES2015;
+ break;
+ case "esnext":
+ out[key] = ts.ModuleKind.ESNext;
+ break;
+ case "none":
+ out[key] = ts.ModuleKind.None;
+ break;
+ case "system":
+ out[key] = ts.ModuleKind.System;
+ break;
+ case "umd":
+ out[key] = ts.ModuleKind.UMD;
+ break;
+ default:
+ throw new TypeError("Unexpected module type");
+ }
+ break;
+ case "target":
+ switch (options[key]) {
+ case "es3":
+ out[key] = ts.ScriptTarget.ES3;
+ break;
+ case "es5":
+ out[key] = ts.ScriptTarget.ES5;
+ break;
+ case "es6":
+ case "es2015":
+ out[key] = ts.ScriptTarget.ES2015;
+ break;
+ case "es2016":
+ out[key] = ts.ScriptTarget.ES2016;
+ break;
+ case "es2017":
+ out[key] = ts.ScriptTarget.ES2017;
+ break;
+ case "es2018":
+ out[key] = ts.ScriptTarget.ES2018;
+ break;
+ case "es2019":
+ out[key] = ts.ScriptTarget.ES2019;
+ break;
+ case "es2020":
+ out[key] = ts.ScriptTarget.ES2020;
+ break;
+ case "esnext":
+ out[key] = ts.ScriptTarget.ESNext;
+ break;
+ default:
+ throw new TypeError("Unexpected emit target.");
+ }
+ break;
+ case "types":
+ const types = options[key];
+ assert(types);
+ files.push(...types);
+ break;
+ default:
+ out[key] = options[key];
+ }
+ }
+ return {
+ options: out,
+ files: files.length ? files : undefined,
+ };
+ }
+
+ const ignoredDiagnostics = [
+ // TS2306: File 'file:///Users/rld/src/deno/cli/tests/subdir/amd_like.js' is
+ // not a module.
+ 2306,
+ // TS1375: 'await' expressions are only allowed at the top level of a file
+ // when that file is a module, but this file has no imports or exports.
+ // Consider adding an empty 'export {}' to make this file a module.
+ 1375,
+ // TS1103: 'for-await-of' statement is only allowed within an async function
+ // or async generator.
+ 1103,
+ // TS2691: An import path cannot end with a '.ts' extension. Consider
+ // importing 'bad-module' instead.
+ 2691,
+ // TS5009: Cannot find the common subdirectory path for the input files.
+ 5009,
+ // TS5055: Cannot write file
+ // 'http://localhost:4545/cli/tests/subdir/mt_application_x_javascript.j4.js'
+ // because it would overwrite input file.
+ 5055,
+ // TypeScript is overly opinionated that only CommonJS modules kinds can
+ // support JSON imports. Allegedly this was fixed in
+ // Microsoft/TypeScript#26825 but that doesn't seem to be working here,
+ // so we will ignore complaints about this compiler setting.
+ 5070,
+ // TS7016: Could not find a declaration file for module '...'. '...'
+ // implicitly has an 'any' type. This is due to `allowJs` being off by
+ // default but importing of a JavaScript module.
+ 7016,
+ ];
+
+ const stats = [];
+ let statsStart = 0;
+
+ function performanceStart() {
+ stats.length = 0;
+ // TODO(kitsonk) replace with performance.mark() when landed
+ statsStart = performance.now();
+ ts.performance.enable();
+ }
+
+ function performanceProgram({
+ program,
+ fileCount,
+ }) {
+ if (program) {
+ if ("getProgram" in program) {
+ program = program.getProgram();
+ }
+ stats.push({ key: "Files", value: program.getSourceFiles().length });
+ stats.push({ key: "Nodes", value: program.getNodeCount() });
+ stats.push({ key: "Identifiers", value: program.getIdentifierCount() });
+ stats.push({ key: "Symbols", value: program.getSymbolCount() });
+ stats.push({ key: "Types", value: program.getTypeCount() });
+ stats.push({
+ key: "Instantiations",
+ value: program.getInstantiationCount(),
+ });
+ } else if (fileCount != null) {
+ stats.push({ key: "Files", value: fileCount });
+ }
+ const programTime = ts.performance.getDuration("Program");
+ const bindTime = ts.performance.getDuration("Bind");
+ const checkTime = ts.performance.getDuration("Check");
+ const emitTime = ts.performance.getDuration("Emit");
+ stats.push({ key: "Parse time", value: programTime });
+ stats.push({ key: "Bind time", value: bindTime });
+ stats.push({ key: "Check time", value: checkTime });
+ stats.push({ key: "Emit time", value: emitTime });
+ stats.push({
+ key: "Total TS time",
+ value: programTime + bindTime + checkTime + emitTime,
+ });
+ }
+
+ function performanceEnd() {
+ // TODO(kitsonk) replace with performance.measure() when landed
+ const duration = performance.now() - statsStart;
+ stats.push({ key: "Compile time", value: duration });
+ return stats;
+ }
+
+ // TODO(Bartlomieju): this check should be done in Rust; there should be no
+ function processConfigureResponse(
+ configResult,
+ configPath,
+ ) {
+ const { ignoredOptions, diagnostics } = configResult;
+ if (ignoredOptions) {
+ console.warn(
+ yellow(`Unsupported compiler options in "${configPath}"\n`) +
+ cyan(` The following options were ignored:\n`) +
+ ` ${ignoredOptions.map((value) => bold(value)).join(", ")}`,
+ );
+ }
+ return diagnostics;
+ }
+
+ function normalizeString(path) {
+ let res = "";
+ let lastSegmentLength = 0;
+ let lastSlash = -1;
+ let dots = 0;
+ let code;
+ for (let i = 0, len = path.length; i <= len; ++i) {
+ if (i < len) code = path.charCodeAt(i);
+ else if (code === CHAR_FORWARD_SLASH) break;
+ else code = CHAR_FORWARD_SLASH;
+
+ if (code === CHAR_FORWARD_SLASH) {
+ if (lastSlash === i - 1 || dots === 1) {
+ // NOOP
+ } else if (lastSlash !== i - 1 && dots === 2) {
+ if (
+ res.length < 2 ||
+ lastSegmentLength !== 2 ||
+ res.charCodeAt(res.length - 1) !== CHAR_DOT ||
+ res.charCodeAt(res.length - 2) !== CHAR_DOT
+ ) {
+ if (res.length > 2) {
+ const lastSlashIndex = res.lastIndexOf("/");
+ if (lastSlashIndex === -1) {
+ res = "";
+ lastSegmentLength = 0;
+ } else {
+ res = res.slice(0, lastSlashIndex);
+ lastSegmentLength = res.length - 1 - res.lastIndexOf("/");
+ }
+ lastSlash = i;
+ dots = 0;
+ continue;
+ } else if (res.length === 2 || res.length === 1) {
+ res = "";
+ lastSegmentLength = 0;
+ lastSlash = i;
+ dots = 0;
+ continue;
+ }
+ }
+ } else {
+ if (res.length > 0) res += "/" + path.slice(lastSlash + 1, i);
+ else res = path.slice(lastSlash + 1, i);
+ lastSegmentLength = i - lastSlash - 1;
+ }
+ lastSlash = i;
+ dots = 0;
+ } else if (code === CHAR_DOT && dots !== -1) {
+ ++dots;
+ } else {
+ dots = -1;
+ }
+ }
+ return res;
+ }
+
+ function commonPath(paths, sep = "/") {
+ const [first = "", ...remaining] = paths;
+ if (first === "" || remaining.length === 0) {
+ return first.substring(0, first.lastIndexOf(sep) + 1);
+ }
+ const parts = first.split(sep);
+
+ let endOfPrefix = parts.length;
+ for (const path of remaining) {
+ const compare = path.split(sep);
+ for (let i = 0; i < endOfPrefix; i++) {
+ if (compare[i] !== parts[i]) {
+ endOfPrefix = i;
+ }
+ }
+
+ if (endOfPrefix === 0) {
+ return "";
+ }
+ }
+ const prefix = parts.slice(0, endOfPrefix).join(sep);
+ return prefix.endsWith(sep) ? prefix : `${prefix}${sep}`;
+ }
+
+ let rootExports;
+
+ function normalizeUrl(rootName) {
+ const match = /^(\S+:\/{2,3})(.+)$/.exec(rootName);
+ if (match) {
+ const [, protocol, path] = match;
+ return `${protocol}${normalizeString(path)}`;
+ } else {
+ return rootName;
+ }
+ }
+
+ function buildBundle(
+ rootName,
+ data,
+ sourceFiles,
+ target,
+ ) {
+ // when outputting to AMD and a single outfile, TypeScript makes up the module
+ // specifiers which are used to define the modules, and doesn't expose them
+ // publicly, so we have to try to replicate
+ const sources = sourceFiles.map((sf) => sf.fileName);
+ const sharedPath = commonPath(sources);
+ rootName = normalizeUrl(rootName)
+ .replace(sharedPath, "")
+ .replace(/\.\w+$/i, "");
+ // If one of the modules requires support for top-level-await, TypeScript will
+ // emit the execute function as an async function. When this is the case we
+ // need to bubble up the TLA to the instantiation, otherwise we instantiate
+ // synchronously.
+ const hasTla = data.match(/execute:\sasync\sfunction\s/);
+ let instantiate;
+ if (rootExports && rootExports.length) {
+ instantiate = hasTla
+ ? `const __exp = await __instantiate("${rootName}", true);\n`
+ : `const __exp = __instantiate("${rootName}", false);\n`;
+ for (const rootExport of rootExports) {
+ if (rootExport === "default") {
+ instantiate += `export default __exp["${rootExport}"];\n`;
+ } else {
+ instantiate +=
+ `export const ${rootExport} = __exp["${rootExport}"];\n`;
+ }
+ }
+ } else {
+ instantiate = hasTla
+ ? `await __instantiate("${rootName}", true);\n`
+ : `__instantiate("${rootName}", false);\n`;
+ }
+ const es5Bundle = target === ts.ScriptTarget.ES3 ||
+ target === ts.ScriptTarget.ES5 ||
+ target === ts.ScriptTarget.ES2015 ||
+ target === ts.ScriptTarget.ES2016;
+ return `${
+ es5Bundle ? SYSTEM_LOADER_ES5 : SYSTEM_LOADER
+ }\n${data}\n${instantiate}`;
+ }
+
+ function setRootExports(program, rootModule) {
+ // get a reference to the type checker, this will let us find symbols from
+ // the AST.
+ const checker = program.getTypeChecker();
+ // get a reference to the main source file for the bundle
+ const mainSourceFile = program.getSourceFile(rootModule);
+ assert(mainSourceFile);
+ // retrieve the internal TypeScript symbol for this AST node
+ const mainSymbol = checker.getSymbolAtLocation(mainSourceFile);
+ if (!mainSymbol) {
+ return;
+ }
+ rootExports = checker
+ .getExportsOfModule(mainSymbol)
+ // .getExportsOfModule includes type only symbols which are exported from
+ // the module, so we need to try to filter those out. While not critical
+ // someone looking at the bundle would think there is runtime code behind
+ // that when there isn't. There appears to be no clean way of figuring that
+ // out, so inspecting SymbolFlags that might be present that are type only
+ .filter(
+ (sym) =>
+ sym.flags & ts.SymbolFlags.Class ||
+ !(
+ sym.flags & ts.SymbolFlags.Interface ||
+ sym.flags & ts.SymbolFlags.TypeLiteral ||
+ sym.flags & ts.SymbolFlags.Signature ||
+ sym.flags & ts.SymbolFlags.TypeParameter ||
+ sym.flags & ts.SymbolFlags.TypeAlias ||
+ sym.flags & ts.SymbolFlags.Type ||
+ sym.flags & ts.SymbolFlags.Namespace ||
+ sym.flags & ts.SymbolFlags.InterfaceExcludes ||
+ sym.flags & ts.SymbolFlags.TypeParameterExcludes ||
+ sym.flags & ts.SymbolFlags.TypeAliasExcludes
+ ),
+ )
+ .map((sym) => sym.getName());
+ }
+
+ function compile({
+ allowJs,
+ buildInfo,
+ config,
+ configPath,
+ rootNames,
+ target,
+ unstable,
+ cwd,
+ sourceFileMap,
+ type,
+ performance,
+ }) {
+ if (performance) {
+ performanceStart();
+ }
+ log(">>> compile start", { rootNames, type: CompilerRequestType[type] });
+
+ // When a programme is emitted, TypeScript will call `writeFile` with
+ // each file that needs to be emitted. The Deno compiler host delegates
+ // this, to make it easier to perform the right actions, which vary
+ // based a lot on the request.
+ const state = {
+ rootNames,
+ emitMap: {},
+ };
+ const host = new IncrementalCompileHost({
+ bundle: false,
+ target,
+ unstable,
+ writeFile: createCompileWriteFile(state),
+ rootNames,
+ buildInfo,
+ });
+ let diagnostics = [];
+
+ host.mergeOptions({ allowJs });
+
+ // if there is a configuration supplied, we need to parse that
+ if (config && config.length && configPath) {
+ const configResult = host.configure(cwd, configPath, config);
+ diagnostics = processConfigureResponse(configResult, configPath) || [];
+ }
+
+ buildSourceFileCache(sourceFileMap);
+ // if there was a configuration and no diagnostics with it, we will continue
+ // to generate the program and possibly emit it.
+ if (diagnostics.length === 0) {
+ const options = host.getCompilationSettings();
+ const program = ts.createIncrementalProgram({
+ rootNames,
+ options,
+ host,
+ });
+
+ // TODO(bartlomieju): check if this is ok
+ diagnostics = [
+ ...program.getConfigFileParsingDiagnostics(),
+ ...program.getSyntacticDiagnostics(),
+ ...program.getOptionsDiagnostics(),
+ ...program.getGlobalDiagnostics(),
+ ...program.getSemanticDiagnostics(),
+ ];
+ diagnostics = diagnostics.filter(
+ ({ code }) => !ignoredDiagnostics.includes(code),
+ );
+
+ // We will only proceed with the emit if there are no diagnostics.
+ if (diagnostics.length === 0) {
+ const emitResult = program.emit();
+ // If `checkJs` is off we still might be compiling entry point JavaScript file
+ // (if it has `.ts` imports), but it won't be emitted. In that case we skip
+ // assertion.
+ if (options.checkJs) {
+ assert(
+ emitResult.emitSkipped === false,
+ "Unexpected skip of the emit.",
+ );
+ }
+ // emitResult.diagnostics is `readonly` in TS3.5+ and can't be assigned
+ // without casting.
+ diagnostics = emitResult.diagnostics;
+ }
+ performanceProgram({ program });
+ }
+
+ log("<<< compile end", { rootNames, type: CompilerRequestType[type] });
+ const stats = performance ? performanceEnd() : undefined;
+
+ return {
+ emitMap: state.emitMap,
+ buildInfo: state.buildInfo,
+ diagnostics: fromTypeScriptDiagnostic(diagnostics),
+ stats,
+ };
+ }
+
+ function transpile({
+ config: configText,
+ configPath,
+ cwd,
+ performance,
+ sourceFiles,
+ }) {
+ if (performance) {
+ performanceStart();
+ }
+ log(">>> transpile start");
+ let compilerOptions;
+ if (configText && configPath && cwd) {
+ const { options, ...response } = configure(
+ DEFAULT_TRANSPILE_OPTIONS,
+ configText,
+ configPath,
+ cwd,
+ );
+ const diagnostics = processConfigureResponse(response, configPath);
+ if (diagnostics && diagnostics.length) {
+ return {
+ diagnostics: fromTypeScriptDiagnostic(diagnostics),
+ emitMap: {},
+ };
+ }
+ compilerOptions = options;
+ } else {
+ compilerOptions = Object.assign({}, DEFAULT_TRANSPILE_OPTIONS);
+ }
+ const emitMap = {};
+ let diagnostics = [];
+ for (const { sourceCode, fileName } of sourceFiles) {
+ const {
+ outputText,
+ sourceMapText,
+ diagnostics: diags,
+ } = ts.transpileModule(sourceCode, {
+ fileName,
+ compilerOptions,
+ reportDiagnostics: true,
+ });
+ if (diags) {
+ diagnostics = diagnostics.concat(...diags);
+ }
+ emitMap[`${fileName}.js`] = { filename: fileName, contents: outputText };
+ // currently we inline source maps, but this is good logic to have if this
+ // ever changes
+ if (sourceMapText) {
+ emitMap[`${fileName}.map`] = {
+ filename: fileName,
+ contents: sourceMapText,
+ };
+ }
+ }
+ performanceProgram({ fileCount: sourceFiles.length });
+ const stats = performance ? performanceEnd() : undefined;
+ log("<<< transpile end");
+ return {
+ diagnostics: fromTypeScriptDiagnostic(diagnostics),
+ emitMap,
+ stats,
+ };
+ }
+
+ function bundle({
+ config,
+ configPath,
+ rootNames,
+ target,
+ unstable,
+ cwd,
+ sourceFileMap,
+ type,
+ }) {
+ if (performance) {
+ performanceStart();
+ }
+ log(">>> bundle start", {
+ rootNames,
+ type: CompilerRequestType[type],
+ });
+
+ // When a programme is emitted, TypeScript will call `writeFile` with
+ // each file that needs to be emitted. The Deno compiler host delegates
+ // this, to make it easier to perform the right actions, which vary
+ // based a lot on the request.
+ const state = {
+ rootNames,
+ bundleOutput: undefined,
+ };
+ const host = new Host({
+ bundle: true,
+ target,
+ unstable,
+ writeFile: createBundleWriteFile(state),
+ });
+ state.host = host;
+ let diagnostics = [];
+
+ // if there is a configuration supplied, we need to parse that
+ if (config && config.length && configPath) {
+ const configResult = host.configure(cwd, configPath, config);
+ diagnostics = processConfigureResponse(configResult, configPath) || [];
+ }
+
+ buildSourceFileCache(sourceFileMap);
+ // if there was a configuration and no diagnostics with it, we will continue
+ // to generate the program and possibly emit it.
+ if (diagnostics.length === 0) {
+ const options = host.getCompilationSettings();
+ const program = ts.createProgram({
+ rootNames,
+ options,
+ host,
+ });
+
+ diagnostics = ts
+ .getPreEmitDiagnostics(program)
+ .filter(({ code }) => !ignoredDiagnostics.includes(code));
+
+ // We will only proceed with the emit if there are no diagnostics.
+ if (diagnostics.length === 0) {
+ // we only support a single root module when bundling
+ assert(rootNames.length === 1);
+ setRootExports(program, rootNames[0]);
+ const emitResult = program.emit();
+ assert(
+ emitResult.emitSkipped === false,
+ "Unexpected skip of the emit.",
+ );
+ // emitResult.diagnostics is `readonly` in TS3.5+ and can't be assigned
+ // without casting.
+ diagnostics = emitResult.diagnostics;
+ }
+ if (performance) {
+ performanceProgram({ program });
+ }
+ }
+
+ let bundleOutput;
+
+ if (diagnostics.length === 0) {
+ assert(state.bundleOutput);
+ bundleOutput = state.bundleOutput;
+ }
+
+ const stats = performance ? performanceEnd() : undefined;
+
+ const result = {
+ bundleOutput,
+ diagnostics: fromTypeScriptDiagnostic(diagnostics),
+ stats,
+ };
+
+ log("<<< bundle end", {
+ rootNames,
+ type: CompilerRequestType[type],
+ });
+
+ return result;
+ }
+
+ function runtimeCompile(
+ request,
+ ) {
+ const { options, rootNames, target, unstable, sourceFileMap } = request;
+
+ log(">>> runtime compile start", {
+ rootNames,
+ });
+
+ // if there are options, convert them into TypeScript compiler options,
+ // and resolve any external file references
+ let convertedOptions;
+ if (options) {
+ const result = convertCompilerOptions(options);
+ convertedOptions = result.options;
+ }
+
+ buildLocalSourceFileCache(sourceFileMap);
+
+ const state = {
+ rootNames,
+ emitMap: {},
+ };
+ const host = new Host({
+ bundle: false,
+ target,
+ writeFile: createRuntimeCompileWriteFile(state),
+ });
+ const compilerOptions = [DEFAULT_RUNTIME_COMPILE_OPTIONS];
+ if (convertedOptions) {
+ compilerOptions.push(convertedOptions);
+ }
+ if (unstable) {
+ compilerOptions.push({
+ lib: [
+ "deno.unstable",
+ ...((convertedOptions && convertedOptions.lib) || ["deno.window"]),
+ ],
+ });
+ }
+
+ host.mergeOptions(...compilerOptions);
+
+ const program = ts.createProgram({
+ rootNames,
+ options: host.getCompilationSettings(),
+ host,
+ });
+
+ const diagnostics = ts
+ .getPreEmitDiagnostics(program)
+ .filter(({ code }) => !ignoredDiagnostics.includes(code));
+
+ const emitResult = program.emit();
+
+ assert(emitResult.emitSkipped === false, "Unexpected skip of the emit.");
+
+ log("<<< runtime compile finish", {
+ rootNames,
+ emitMap: Object.keys(state.emitMap),
+ });
+
+ const maybeDiagnostics = diagnostics.length
+ ? fromTypeScriptDiagnostic(diagnostics).items
+ : [];
+
+ return {
+ diagnostics: maybeDiagnostics,
+ emitMap: state.emitMap,
+ };
+ }
+
+ function runtimeBundle(request) {
+ const { options, rootNames, target, unstable, sourceFileMap } = request;
+
+ log(">>> runtime bundle start", {
+ rootNames,
+ });
+
+ // if there are options, convert them into TypeScript compiler options,
+ // and resolve any external file references
+ let convertedOptions;
+ if (options) {
+ const result = convertCompilerOptions(options);
+ convertedOptions = result.options;
+ }
+
+ buildLocalSourceFileCache(sourceFileMap);
+
+ const state = {
+ rootNames,
+ bundleOutput: undefined,
+ };
+ const host = new Host({
+ bundle: true,
+ target,
+ writeFile: createBundleWriteFile(state),
+ });
+ state.host = host;
+
+ const compilerOptions = [DEFAULT_RUNTIME_COMPILE_OPTIONS];
+ if (convertedOptions) {
+ compilerOptions.push(convertedOptions);
+ }
+ if (unstable) {
+ compilerOptions.push({
+ lib: [
+ "deno.unstable",
+ ...((convertedOptions && convertedOptions.lib) || ["deno.window"]),
+ ],
+ });
+ }
+ compilerOptions.push(DEFAULT_BUNDLER_OPTIONS);
+ host.mergeOptions(...compilerOptions);
+
+ const program = ts.createProgram({
+ rootNames,
+ options: host.getCompilationSettings(),
+ host,
+ });
+
+ setRootExports(program, rootNames[0]);
+ const diagnostics = ts
+ .getPreEmitDiagnostics(program)
+ .filter(({ code }) => !ignoredDiagnostics.includes(code));
+
+ const emitResult = program.emit();
+
+ assert(emitResult.emitSkipped === false, "Unexpected skip of the emit.");
+
+ log("<<< runtime bundle finish", {
+ rootNames,
+ });
+
+ const maybeDiagnostics = diagnostics.length
+ ? fromTypeScriptDiagnostic(diagnostics).items
+ : [];
+
+ return {
+ diagnostics: maybeDiagnostics,
+ output: state.bundleOutput,
+ };
+ }
+
+ function runtimeTranspile(
+ request,
+ ) {
+ const result = {};
+ const { sources, options } = request;
+ const compilerOptions = options
+ ? Object.assign(
+ {},
+ DEFAULT_RUNTIME_TRANSPILE_OPTIONS,
+ convertCompilerOptions(options).options,
+ )
+ : DEFAULT_RUNTIME_TRANSPILE_OPTIONS;
+
+ for (const [fileName, inputText] of Object.entries(sources)) {
+ const { outputText: source, sourceMapText: map } = ts.transpileModule(
+ inputText,
+ {
+ fileName,
+ compilerOptions,
+ },
+ );
+ result[fileName] = { source, map };
+ }
+ return Promise.resolve(result);
+ }
+
+ async function tsCompilerOnMessage({
+ data: request,
+ }) {
+ switch (request.type) {
+ case CompilerRequestType.Compile: {
+ const result = compile(request);
+ globalThis.postMessage(result);
+ break;
+ }
+ case CompilerRequestType.Transpile: {
+ const result = transpile(request);
+ globalThis.postMessage(result);
+ break;
+ }
+ case CompilerRequestType.Bundle: {
+ const result = bundle(request);
+ globalThis.postMessage(result);
+ break;
+ }
+ case CompilerRequestType.RuntimeCompile: {
+ const result = runtimeCompile(request);
+ globalThis.postMessage(result);
+ break;
+ }
+ case CompilerRequestType.RuntimeBundle: {
+ const result = runtimeBundle(request);
+ globalThis.postMessage(result);
+ break;
+ }
+ case CompilerRequestType.RuntimeTranspile: {
+ const result = await runtimeTranspile(request);
+ globalThis.postMessage(result);
+ break;
+ }
+ default:
+ log(
+ `!!! unhandled CompilerRequestType: ${request.type} (${
+ CompilerRequestType[request.type]
+ })`,
+ );
+ }
+ // Shutdown after single request
+ globalThis.close();
+ }
+
+ function bootstrapTsCompilerRuntime() {
+ globalThis.bootstrap.workerRuntime("TS", false);
+ globalThis.onmessage = tsCompilerOnMessage;
+ }
+
+ Object.defineProperties(globalThis, {
+ bootstrap: {
+ value: {
+ ...globalThis.bootstrap,
+ tsCompilerRuntime: bootstrapTsCompilerRuntime,
+ },
+ configurable: true,
+ writable: true,
+ },
+ });
+})(this);