diff options
Diffstat (limited to 'js')
-rw-r--r-- | js/compiler.ts | 758 | ||||
-rw-r--r-- | js/os.ts | 45 |
2 files changed, 244 insertions, 559 deletions
diff --git a/js/compiler.ts b/js/compiler.ts index d830dd711..928fa09d8 100644 --- a/js/compiler.ts +++ b/js/compiler.ts @@ -1,76 +1,40 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -import * as ts from "typescript"; import * as msg from "gen/cli/msg_generated"; -import { window } from "./window"; -import { assetSourceCode } from "./assets"; -import { bold, cyan, yellow } from "./colors"; -import { Console } from "./console"; import { core } from "./core"; -import { cwd } from "./dir"; -import { sendSync } from "./dispatch"; import * as flatbuffers from "./flatbuffers"; +import { sendSync } from "./dispatch"; +import { TextDecoder } from "./text_encoding"; +import * as ts from "typescript"; import * as os from "./os"; -import { TextDecoder, TextEncoder } from "./text_encoding"; -import { clearTimer, setTimeout } from "./timers"; +import { bold, cyan, yellow } from "./colors"; +import { window } from "./window"; import { postMessage, workerClose, workerMain } from "./workers"; -import { assert, log, notImplemented } from "./util"; - -const EOL = "\n"; -const ASSETS = "$asset$"; -const LIB_RUNTIME = `${ASSETS}/lib.deno_runtime.d.ts`; +import { Console } from "./console"; +import { assert, notImplemented } from "./util"; +import * as util from "./util"; +import { cwd } from "./dir"; +import { assetSourceCode } from "./assets"; -// An instance of console +// Startup boilerplate. This is necessary because the compiler has its own +// snapshot. (It would be great if we could remove these things or centralize +// them somewhere else.) const console = new Console(core.print); - -/** 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 original resolved resource name. - * Path to cached module file or URL from which dependency was retrieved - */ -type ModuleId = 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; -/** The original source code */ -type SourceCode = string; -/** The output source map */ -type SourceMap = string; - -/** The format of the work message payload coming from the privileged side */ -interface CompilerLookup { - specifier: ModuleSpecifier; - referrer: ContainingFile; - cmdId: number; +window.console = console; +window.workerMain = workerMain; +export default function denoMain(): void { + os.start("TS"); } -/** Abstraction of the APIs required from the `os` module so they can be - * easily mocked. - */ -interface Os { - fetchModuleMetaData: typeof os.fetchModuleMetaData; - exit: typeof os.exit; - noColor: typeof os.noColor; -} +const ASSETS = "$asset$"; +const OUT_DIR = "$deno$"; -/** Abstraction of the APIs required from the `typescript` module so they can - * be easily mocked. - */ -interface Ts { - convertCompilerOptionsFromJson: typeof ts.convertCompilerOptionsFromJson; - createLanguageService: typeof ts.createLanguageService; - formatDiagnosticsWithColorAndContext: typeof ts.formatDiagnosticsWithColorAndContext; - formatDiagnostics: typeof ts.formatDiagnostics; - parseConfigFileTextToJson: typeof ts.parseConfigFileTextToJson; +/** The format of the work message payload coming from the privileged side */ +interface CompilerReq { + rootNames: string[]; + // TODO(ry) add compiler config to this interface. + // options: ts.CompilerOptions; + configPath?: string; + config?: string; } /** Options that either do nothing in Deno, or would cause undesired behavior @@ -134,48 +98,68 @@ const ignoredCompilerOptions: ReadonlyArray<string> = [ "watch" ]; -/** 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. - */ -class ModuleMetaData implements ts.IScriptSnapshot { - public scriptVersion = ""; - - constructor( - public readonly moduleId: ModuleId, - public readonly fileName: ModuleFileName, - public readonly mediaType: msg.MediaType, - public readonly sourceCode: SourceCode = "", - public outputCode: OutputCode = "", - public sourceMap: SourceMap = "" - ) { - if (outputCode !== "" || fileName.endsWith(".d.ts")) { - this.scriptVersion = "1"; - } - } - - /** TypeScript IScriptSnapshot Interface */ - - public getText(start: number, end: number): string { - return start === 0 && end === this.sourceCode.length - ? this.sourceCode - : this.sourceCode.substring(start, end); - } +interface ModuleMetaData { + moduleName: string | undefined; + filename: string | undefined; + mediaType: msg.MediaType; + sourceCode: string | undefined; +} - public getLength(): number { - return this.sourceCode.length; - } +function fetchModuleMetaData( + specifier: string, + referrer: string +): ModuleMetaData { + util.log("compiler.fetchModuleMetaData", { specifier, referrer }); + // Send FetchModuleMetaData message + const builder = flatbuffers.createBuilder(); + const specifier_ = builder.createString(specifier); + const referrer_ = builder.createString(referrer); + const inner = msg.FetchModuleMetaData.createFetchModuleMetaData( + builder, + specifier_, + referrer_ + ); + const baseRes = sendSync(builder, msg.Any.FetchModuleMetaData, inner); + assert(baseRes != null); + assert( + msg.Any.FetchModuleMetaDataRes === baseRes!.innerType(), + `base.innerType() unexpectedly is ${baseRes!.innerType()}` + ); + const fetchModuleMetaDataRes = new msg.FetchModuleMetaDataRes(); + assert(baseRes!.inner(fetchModuleMetaDataRes) != null); + const dataArray = fetchModuleMetaDataRes.dataArray(); + const decoder = new TextDecoder(); + const sourceCode = dataArray ? decoder.decode(dataArray) : undefined; + // flatbuffers returns `null` for an empty value, this does not fit well with + // idiomatic TypeScript under strict null checks, so converting to `undefined` + return { + moduleName: fetchModuleMetaDataRes.moduleName() || undefined, + filename: fetchModuleMetaDataRes.filename() || undefined, + mediaType: fetchModuleMetaDataRes.mediaType(), + sourceCode + }; +} - public getChangeRange(): undefined { - // Required `IScriptSnapshot` API, but not implemented/needed in deno - return undefined; - } +/** For caching source map and compiled js */ +function cache(extension: string, moduleId: string, contents: string): void { + util.log("compiler.cache", moduleId); + const builder = flatbuffers.createBuilder(); + const extension_ = builder.createString(extension); + const moduleId_ = builder.createString(moduleId); + const contents_ = builder.createString(contents); + const inner = msg.Cache.createCache( + builder, + extension_, + moduleId_, + contents_ + ); + const baseRes = sendSync(builder, msg.Any.Cache, inner); + assert(baseRes == null); } /** Returns the TypeScript Extension enum for a given media type. */ function getExtension( - fileName: ModuleFileName, + fileName: string, mediaType: msg.MediaType ): ts.Extension { switch (mediaType) { @@ -191,294 +175,34 @@ function getExtension( } } -/** Generate output code for a provided JSON string along with its source. */ -function jsonEsmTemplate( - jsonString: string, - sourceFileName: string -): OutputCode { - return ( - `const _json = JSON.parse(\`${jsonString}\`);\n` + - `export default _json;\n` + - `//# sourceURL=${sourceFileName}\n` - ); -} - -/** 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. - */ -class Compiler implements ts.LanguageServiceHost, ts.FormatDiagnosticsHost { - // 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 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 +class Host implements ts.CompilerHost { private readonly _options: ts.CompilerOptions = { allowJs: true, allowNonTsExtensions: true, - checkJs: true, + checkJs: false, esModuleInterop: true, module: ts.ModuleKind.ESNext, - outDir: "$deno$", + outDir: OUT_DIR, resolveJsonModule: true, sourceMap: 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; - - private readonly _assetsSourceCode: { [key: string]: string }; - - /** 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.get(fileName) || - (fileName.startsWith(ASSETS) - ? this._resolveModule(fileName, "") - : undefined) - ); - } - - /** Log TypeScript diagnostics to the console and exit */ - private _logDiagnostics(diagnostics: ts.Diagnostic[]): never { - const errMsg = this._os.noColor - ? this._ts.formatDiagnostics(diagnostics, this) - : this._ts.formatDiagnosticsWithColorAndContext(diagnostics, this); - - console.log(errMsg); - // TODO The compiler isolate shouldn't exit. Errors should be forwarded to - // to the caller and the caller exit. - return this._os.exit(1); - } - - /** 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`. - */ - private _resolveFileName( - moduleSpecifier: ModuleSpecifier, - containingFile: ContainingFile - ): ModuleFileName | undefined { - this._log("compiler._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`. - */ - private _resolveModule( - moduleSpecifier: ModuleSpecifier, - containingFile: ContainingFile - ): ModuleMetaData { - this._log("compiler._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 moduleId: ModuleId | undefined; - let mediaType = msg.MediaType.Unknown; - let sourceCode: SourceCode | 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 - moduleId = moduleSpecifier.split("/").pop()!; - const assetName = moduleId.includes(".") ? moduleId : `${moduleId}.d.ts`; - assert( - assetName in this._assetsSourceCode, - `No such asset "${assetName}"` - ); - mediaType = msg.MediaType.TypeScript; - sourceCode = this._assetsSourceCode[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 = this._os.fetchModuleMetaData( - moduleSpecifier, - containingFile - ); - moduleId = fetchResponse.moduleName; - fileName = fetchResponse.filename; - mediaType = fetchResponse.mediaType; - sourceCode = fetchResponse.sourceCode; - } - assert(moduleId != null, "No module ID."); - assert(fileName != null, "No file name."); - assert( - mediaType !== msg.MediaType.Unknown, - `Unknown media type for: "${moduleSpecifier}" from "${containingFile}".` - ); - this._log( - "resolveModule sourceCode length:", - sourceCode && sourceCode.length - ); - this._log("resolveModule has media type:", msg.MediaType[mediaType]); - // fileName is asserted above, but TypeScript does not track so not null - this._setFileName(moduleSpecifier, containingFile, fileName!); - if (fileName && this._moduleMetaDataMap.has(fileName)) { - return this._moduleMetaDataMap.get(fileName)!; - } - const moduleMetaData = new ModuleMetaData( - moduleId!, - fileName!, - mediaType, - sourceCode - ); - this._moduleMetaDataMap.set(fileName!, moduleMetaData); - 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. - */ - private _setFileName( - moduleSpecifier: ModuleSpecifier, - containingFile: ContainingFile, - fileName: ModuleFileName - ): void { - this._log("compiler._setFileName", { moduleSpecifier, containingFile }); - let innerMap = this._fileNamesMap.get(containingFile); - if (!innerMap) { - innerMap = new Map(); - this._fileNamesMap.set(containingFile, innerMap); - } - innerMap.set(moduleSpecifier, fileName); - } - - constructor(assetsSourceCode: { [key: string]: string }) { - this._assetsSourceCode = assetsSourceCode; - this._service = this._ts.createLanguageService(this); - } - - // Deno specific compiler API - - /** Retrieve the output of the TypeScript compiler for a given module. - */ - compile( - moduleSpecifier: ModuleSpecifier, - containingFile: ContainingFile - ): { outputCode: OutputCode; sourceMap: SourceMap } { - this._log("compiler.compile", { moduleSpecifier, containingFile }); - const moduleMetaData = this._resolveModule(moduleSpecifier, containingFile); - const { fileName, mediaType, sourceCode } = moduleMetaData; - this._scriptFileNames = [fileName]; - let outputCode: string; - let sourceMap = ""; - // Instead of using TypeScript to transpile JSON modules, we will just do - // it directly. - if (mediaType === msg.MediaType.Json) { - outputCode = moduleMetaData.outputCode = jsonEsmTemplate( - sourceCode, - fileName - ); - } else { - const service = this._service; - assert( - mediaType === msg.MediaType.TypeScript || - mediaType === msg.MediaType.JavaScript - ); - const output = service.getEmitOutput(fileName); - - // Get the relevant diagnostics - this is 3x faster than - // `getPreEmitDiagnostics`. - const diagnostics = [ - // 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. - ...service - .getCompilerOptionsDiagnostics() - .filter((diagnostic): boolean => diagnostic.code !== 5070), - ...service.getSyntacticDiagnostics(fileName), - ...service.getSemanticDiagnostics(fileName) - ]; - if (diagnostics.length > 0) { - this._logDiagnostics(diagnostics); - } - - assert( - !output.emitSkipped, - "The emit was skipped for an unknown reason." - ); - - assert( - output.outputFiles.length === 2, - `Expected 2 files to be emitted, got ${output.outputFiles.length}.` - ); - - const [sourceMapFile, outputFile] = output.outputFiles; - assert( - sourceMapFile.name.endsWith(".map"), - "Expected first emitted file to be a source map" - ); - assert( - outputFile.name.endsWith(".js"), - "Expected second emitted file to be JavaScript" - ); - outputCode = moduleMetaData.outputCode = `${ - outputFile.text - }\n//# sourceURL=${fileName}`; - sourceMap = moduleMetaData.sourceMap = sourceMapFile.text; - } - - moduleMetaData.scriptVersion = "1"; - return { outputCode, sourceMap }; - } /** Take a configuration string, parse it, and use it to merge with the * compiler's configuration options. The method returns an array of compiler * options which were ignored, or `undefined`. */ configure(path: string, configurationText: string): string[] | undefined { - this._log("compile.configure", path); - const { config, error } = this._ts.parseConfigFileTextToJson( + util.log("compile.configure", path); + const { config, error } = ts.parseConfigFileTextToJson( path, configurationText ); if (error) { this._logDiagnostics([error]); } - const { options, errors } = this._ts.convertCompilerOptionsFromJson( + const { options, errors } = ts.convertCompilerOptionsFromJson( config.compilerOptions, cwd() ); @@ -499,195 +223,201 @@ class Compiler implements ts.LanguageServiceHost, ts.FormatDiagnosticsHost { return ignoredOptions.length ? ignoredOptions : undefined; } - // TypeScript Language Service and Format Diagnostic Host API - - getCanonicalFileName(fileName: string): string { - this._log("getCanonicalFileName", fileName); - return fileName; - } - getCompilationSettings(): ts.CompilerOptions { - this._log("getCompilationSettings()"); + util.log("getCompilationSettings()"); return this._options; } - getNewLine(): string { - return EOL; + /** Log TypeScript diagnostics to the console and exit */ + _logDiagnostics(diagnostics: ReadonlyArray<ts.Diagnostic>): never { + const errMsg = os.noColor + ? ts.formatDiagnostics(diagnostics, this) + : ts.formatDiagnosticsWithColorAndContext(diagnostics, this); + + console.log(errMsg); + // TODO The compiler isolate shouldn't call os.exit(). (In fact, it + // shouldn't even have access to call that op.) Errors should be forwarded + // to to the caller and the caller exit. + return os.exit(1); } - 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 `.compile()` - return this._scriptFileNames; + fileExists(_fileName: string): boolean { + return notImplemented(); } - getScriptKind(fileName: ModuleFileName): ts.ScriptKind { - this._log("getScriptKind()", fileName); - const moduleMetaData = this._getModuleMetaData(fileName); - if (moduleMetaData) { - switch (moduleMetaData.mediaType) { - case msg.MediaType.TypeScript: - return ts.ScriptKind.TS; - case msg.MediaType.JavaScript: - return ts.ScriptKind.JS; - case msg.MediaType.Json: - return ts.ScriptKind.JSON; - default: - return this._options.allowJs ? ts.ScriptKind.JS : ts.ScriptKind.TS; - } - } else { - return this._options.allowJs ? ts.ScriptKind.JS : ts.ScriptKind.TS; + readFile(_fileName: string): string | undefined { + return notImplemented(); + } + + getSourceFile( + fileName: string, + languageVersion: ts.ScriptTarget, + onError?: (message: string) => void, + shouldCreateNewSourceFile?: boolean + ): ts.SourceFile | undefined { + assert(!shouldCreateNewSourceFile); + util.log("getSourceFile", fileName); + const moduleMetaData = this._resolveModule(fileName, "."); + if (!moduleMetaData || !moduleMetaData.sourceCode) { + return undefined; } + return ts.createSourceFile( + fileName, + moduleMetaData.sourceCode, + languageVersion + ); } - getScriptVersion(fileName: ModuleFileName): string { - const moduleMetaData = this._getModuleMetaData(fileName); - const version = (moduleMetaData && moduleMetaData.scriptVersion) || ""; - this._log("getScriptVersion()", fileName, version); - return version; + getDefaultLibFileName(_options: ts.CompilerOptions): string { + return ASSETS + "/lib.deno_runtime.d.ts"; } - getScriptSnapshot(fileName: ModuleFileName): ts.IScriptSnapshot | undefined { - this._log("getScriptSnapshot()", fileName); - return this._getModuleMetaData(fileName); + writeFile( + fileName: string, + data: string, + writeByteOrderMark: boolean, + onError?: (message: string) => void, + sourceFiles?: ReadonlyArray<ts.SourceFile> + ): void { + util.log("writeFile", fileName); + assert(sourceFiles != null && sourceFiles.length == 1); + const sourceFileName = sourceFiles![0].fileName; + + if (fileName.endsWith(".map")) { + // Source Map + cache(".map", sourceFileName, data); + } else if (fileName.endsWith(".js") || fileName.endsWith(".json")) { + // Compiled JavaScript + cache(".js", sourceFileName, data); + } else { + assert(false, "Trying to cache unhandled file type " + fileName); + } } getCurrentDirectory(): string { - this._log("getCurrentDirectory()"); return ""; } - getDefaultLibFileName(): string { - this._log("getDefaultLibFileName()"); - const moduleSpecifier = LIB_RUNTIME; - const moduleMetaData = this._getModuleMetaData(moduleSpecifier); - assert(moduleMetaData != null); - return moduleMetaData!.fileName; + getCanonicalFileName(fileName: string): string { + // console.log("getCanonicalFileName", fileName); + return fileName; } useCaseSensitiveFileNames(): boolean { - this._log("useCaseSensitiveFileNames()"); return true; } - readFile(path: string): string | undefined { - this._log("readFile()", path); - return notImplemented(); + getNewLine(): string { + return "\n"; } - fileExists(fileName: string): boolean { - const moduleMetaData = this._getModuleMetaData(fileName); - const exists = moduleMetaData != null; - this._log("fileExists()", fileName, exists); - return exists; + resolveModuleNames( + moduleNames: string[], + containingFile: string + ): Array<ts.ResolvedModuleFull | undefined> { + util.log("resolveModuleNames()", { moduleNames, containingFile }); + return moduleNames.map( + (moduleName): ts.ResolvedModuleFull | undefined => { + const moduleMetaData = this._resolveModule(moduleName, containingFile); + if (moduleMetaData.moduleName) { + const resolvedFileName = moduleMetaData.moduleName; + // This flags to the compiler to not go looking to transpile functional + // code, anything that is in `/$asset$/` is just library code + const isExternalLibraryImport = moduleName.startsWith(ASSETS); + const r = { + resolvedFileName, + isExternalLibraryImport, + extension: getExtension(resolvedFileName, moduleMetaData.mediaType) + }; + return r; + } else { + return undefined; + } + } + ); } - resolveModuleNames( - moduleNames: ModuleSpecifier[], - containingFile: ContainingFile - ): Array<ts.ResolvedModuleFull | ts.ResolvedModule> { - this._log("resolveModuleNames()", { moduleNames, containingFile }); - const resolvedModuleNames: ts.ResolvedModuleFull[] = []; - for (const moduleName of moduleNames) { - const moduleMetaData = this._resolveModule(moduleName, 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 - const resolvedFileName = moduleMetaData.fileName || ""; - // 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); - resolvedModuleNames.push({ - resolvedFileName, - isExternalLibraryImport, - extension: getExtension(resolvedFileName, moduleMetaData.mediaType) - }); + private _resolveModule(specifier: string, referrer: string): ModuleMetaData { + // Handle built-in assets specially. + if (specifier.startsWith(ASSETS)) { + const moduleName = specifier.split("/").pop()!; + const assetName = moduleName.includes(".") + ? moduleName + : `${moduleName}.d.ts`; + assert(assetName in assetSourceCode, `No such asset "${assetName}"`); + const sourceCode = assetSourceCode[assetName]; + return { + moduleName, + filename: specifier, + mediaType: msg.MediaType.TypeScript, + sourceCode + }; } - return resolvedModuleNames; + return fetchModuleMetaData(specifier, referrer); } } -const compiler = new Compiler(assetSourceCode); - -// set global objects for compiler web worker -window.clearTimeout = clearTimer; -window.console = console; -window.postMessage = postMessage; -window.setTimeout = setTimeout; -window.workerMain = workerMain; -window.close = workerClose; -window.TextDecoder = TextDecoder; -window.TextEncoder = TextEncoder; - // provide the "main" function that will be called by the privileged side when // lazy instantiating the compiler web worker window.compilerMain = function compilerMain(): void { // workerMain should have already been called since a compiler is a worker. - window.onmessage = ({ data }: { data: CompilerLookup }): void => { - const { specifier, referrer, cmdId } = data; - - try { - const result = compiler.compile(specifier, referrer); - postMessage({ - success: true, - cmdId, - data: result - }); - } catch (e) { - postMessage({ - success: false, - cmdId, - data: JSON.parse(core.errorToJSON(e)) - }); + window.onmessage = ({ data }: { data: CompilerReq }): void => { + const { rootNames, configPath, config } = data; + const host = new Host(); + if (config && config.length) { + const ignoredOptions = host.configure(configPath!, config); + if (ignoredOptions) { + console.warn( + yellow(`Unsupported compiler options in "${configPath}"\n`) + + cyan(` The following options were ignored:\n`) + + ` ${ignoredOptions + .map((value): string => bold(value)) + .join(", ")}` + ); + } } - }; -}; -const decoder = new TextDecoder(); + const options = host.getCompilationSettings(); + const program = ts.createProgram(rootNames, options, host); + const emitResult = program!.emit(); + + // TODO(ry) Print diagnostics in Rust. + // https://github.com/denoland/deno/pull/2310 + + const diagnostics = ts + .getPreEmitDiagnostics(program) + .concat(emitResult.diagnostics) + .filter( + ({ code }): boolean => { + if (code === 2649) return false; + // TS2691: An import path cannot end with a '.ts' extension. Consider + // importing 'bad-module' instead. + if (code === 2691) return false; + // TS5009: Cannot find the common subdirectory path for the input files. + if (code === 5009) return false; + // TS5055: Cannot write file + // 'http://localhost:4545/tests/subdir/mt_application_x_javascript.j4.js' + // because it would overwrite input file. + if (code === 5055) return false; + // 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. + if (code === 5070) return false; + return true; + } + ); -// Perform the op to retrieve the compiler configuration if there was any -// provided on startup. -function getCompilerConfig( - compilerType: string -): { path: string; data: string } { - const builder = flatbuffers.createBuilder(); - const compilerType_ = builder.createString(compilerType); - msg.CompilerConfig.startCompilerConfig(builder); - msg.CompilerConfig.addCompilerType(builder, compilerType_); - const inner = msg.CompilerConfig.endCompilerConfig(builder); - const baseRes = sendSync(builder, msg.Any.CompilerConfig, inner); - assert(baseRes != null); - assert(msg.Any.CompilerConfigRes === baseRes!.innerType()); - const res = new msg.CompilerConfigRes(); - assert(baseRes!.inner(res) != null); - - // the privileged side does not normalize path separators in windows, so we - // will normalize them here - const path = res.path()!.replace(/\\/g, "/"); - assert(path != null); - const dataArray = res.dataArray()!; - assert(dataArray != null); - const data = decoder.decode(dataArray); - return { path, data }; -} + if (diagnostics.length > 0) { + host._logDiagnostics(diagnostics); + // The above _logDiagnostics calls os.exit(). The return is here just for + // clarity. + return; + } -export default function denoMain(): void { - os.start("TS"); + postMessage(emitResult); - const { path, data } = getCompilerConfig("typescript"); - if (data.length) { - const ignoredOptions = compiler.configure(path, data); - if (ignoredOptions) { - console.warn( - yellow(`Unsupported compiler options in "${path}"\n`) + - cyan(` The following options were ignored:\n`) + - ` ${ignoredOptions.map((value): string => bold(value)).join(", ")}` - ); - } - } -} + // The compiler isolate exits after a single messsage. + workerClose(); + }; +}; @@ -3,7 +3,6 @@ import * as msg from "gen/cli/msg_generated"; import { core } from "./core"; import { handleAsyncMsgFromRust, sendSync } from "./dispatch"; import * as flatbuffers from "./flatbuffers"; -import { TextDecoder } from "./text_encoding"; import { assert } from "./util"; import * as util from "./util"; import { window } from "./window"; @@ -24,13 +23,6 @@ function setGlobals(pid_: number, noColor_: boolean, execPath_: string): void { execPath = execPath_; } -interface ResponseModuleMetaData { - moduleName: string | undefined; - filename: string | undefined; - mediaType: msg.MediaType; - sourceCode: string | undefined; -} - /** Check if running in terminal. * * console.log(Deno.isTTY().stdout); @@ -54,43 +46,6 @@ export function exit(exitCode = 0): never { return util.unreachable(); } -const decoder = new TextDecoder(); - -// @internal -export function fetchModuleMetaData( - specifier: string, - referrer: string -): ResponseModuleMetaData { - util.log("os.fetchModuleMetaData", { specifier, referrer }); - // Send FetchModuleMetaData message - const builder = flatbuffers.createBuilder(); - const specifier_ = builder.createString(specifier); - const referrer_ = builder.createString(referrer); - const inner = msg.FetchModuleMetaData.createFetchModuleMetaData( - builder, - specifier_, - referrer_ - ); - const baseRes = sendSync(builder, msg.Any.FetchModuleMetaData, inner); - assert(baseRes != null); - assert( - msg.Any.FetchModuleMetaDataRes === baseRes!.innerType(), - `base.innerType() unexpectedly is ${baseRes!.innerType()}` - ); - const fetchModuleMetaDataRes = new msg.FetchModuleMetaDataRes(); - assert(baseRes!.inner(fetchModuleMetaDataRes) != null); - const dataArray = fetchModuleMetaDataRes.dataArray(); - const sourceCode = dataArray ? decoder.decode(dataArray) : undefined; - // flatbuffers returns `null` for an empty value, this does not fit well with - // idiomatic TypeScript under strict null checks, so converting to `undefined` - return { - moduleName: fetchModuleMetaDataRes.moduleName() || undefined, - filename: fetchModuleMetaDataRes.filename() || undefined, - mediaType: fetchModuleMetaDataRes.mediaType(), - sourceCode - }; -} - function setEnv(key: string, value: string): void { const builder = flatbuffers.createBuilder(); const key_ = builder.createString(key); |