diff options
Diffstat (limited to 'js')
-rw-r--r-- | js/compiler.ts | 120 | ||||
-rw-r--r-- | js/compiler_test.ts | 113 | ||||
-rw-r--r-- | js/os.ts | 22 | ||||
-rw-r--r-- | js/types.ts | 8 |
4 files changed, 196 insertions, 67 deletions
diff --git a/js/compiler.ts b/js/compiler.ts index e79df83ea..c872afa14 100644 --- a/js/compiler.ts +++ b/js/compiler.ts @@ -1,5 +1,6 @@ // Copyright 2018 the Deno authors. All rights reserved. MIT license. import * as ts from "typescript"; +import { MediaType } from "gen/msg_generated"; import { assetSourceCode } from "./assets"; // tslint:disable-next-line:no-circular-imports import * as deno from "./deno"; @@ -85,6 +86,7 @@ export class ModuleMetaData implements ts.IScriptSnapshot { constructor( public readonly moduleId: ModuleId, public readonly fileName: ModuleFileName, + public readonly mediaType: MediaType, public readonly sourceCode: SourceCode = "", public outputCode: OutputCode = "" ) { @@ -107,6 +109,23 @@ export class ModuleMetaData implements ts.IScriptSnapshot { } } +function getExtension( + fileName: ModuleFileName, + mediaType: MediaType +): ts.Extension | undefined { + switch (mediaType) { + case MediaType.JavaScript: + return ts.Extension.Js; + case MediaType.TypeScript: + return fileName.endsWith(".d.ts") ? ts.Extension.Dts : ts.Extension.Ts; + case MediaType.Json: + return ts.Extension.Json; + case MediaType.Unknown: + default: + return undefined; + } +} + /** 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. @@ -319,17 +338,6 @@ export class DenoCompiler return undefined; } - /** Resolve the `fileName` for a given `moduleSpecifier` and - * `containingFile` - */ - private _resolveModuleName( - moduleSpecifier: ModuleSpecifier, - containingFile: ContainingFile - ): ModuleFileName | undefined { - const moduleMetaData = this.resolveModule(moduleSpecifier, containingFile); - return moduleMetaData ? moduleMetaData.fileName : undefined; - } - /** 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. @@ -479,9 +487,10 @@ export class DenoCompiler if (fileName && this._moduleMetaDataMap.has(fileName)) { return this._moduleMetaDataMap.get(fileName)!; } - let moduleId: ModuleId; - let sourceCode: SourceCode; - let outputCode: OutputCode | null; + let moduleId: ModuleId | undefined; + let mediaType = MediaType.Unknown; + let sourceCode: SourceCode | undefined; + let outputCode: OutputCode | undefined; if ( moduleSpecifier.startsWith(ASSETS) || containingFile.startsWith(ASSETS) @@ -492,6 +501,7 @@ export class DenoCompiler moduleId = moduleSpecifier.split("/").pop()!; const assetName = moduleId.includes(".") ? moduleId : `${moduleId}.d.ts`; assert(assetName in assetSourceCode, `No such asset "${assetName}"`); + mediaType = MediaType.TypeScript; sourceCode = assetSourceCode[assetName]; fileName = `${ASSETS}/${assetName}`; outputCode = ""; @@ -499,25 +509,38 @@ export class DenoCompiler // 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.codeFetch(moduleSpecifier, containingFile); - moduleId = fetchResponse.moduleName!; - fileName = fetchResponse.filename!; - sourceCode = fetchResponse.sourceCode!; - outputCode = fetchResponse.outputCode!; + moduleId = fetchResponse.moduleName; + fileName = fetchResponse.filename; + mediaType = fetchResponse.mediaType; + sourceCode = fetchResponse.sourceCode; + outputCode = fetchResponse.outputCode; } - assert(sourceCode!.length > 0); - this._log("resolveModule sourceCode length:", sourceCode.length); - this._log("resolveModule has outputCode:", outputCode! != null); - this._setFileName(moduleSpecifier, containingFile, fileName); + assert(moduleId != null, "No module ID."); + assert(fileName != null, "No file name."); + assert(sourceCode ? sourceCode.length > 0 : false, "No source code."); + assert( + mediaType !== MediaType.Unknown, + `Unknown media type for: "${moduleSpecifier}" from "${containingFile}".` + ); + this._log( + "resolveModule sourceCode length:", + sourceCode && sourceCode.length + ); + this._log("resolveModule has outputCode:", outputCode != null); + this._log("resolveModule has media type:", 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, + moduleId!, + fileName!, + mediaType, sourceCode, outputCode ); - this._moduleMetaDataMap.set(fileName, moduleMetaData); + this._moduleMetaDataMap.set(fileName!, moduleMetaData); return moduleMetaData; } @@ -563,16 +586,20 @@ export class DenoCompiler getScriptKind(fileName: ModuleFileName): ts.ScriptKind { this._log("getScriptKind()", fileName); - const suffix = fileName.substr(fileName.lastIndexOf(".") + 1); - switch (suffix) { - case "ts": - return ts.ScriptKind.TS; - case "js": - return ts.ScriptKind.JS; - case "json": - return ts.ScriptKind.JSON; - default: - return this._options.allowJs ? ts.ScriptKind.JS : ts.ScriptKind.TS; + const moduleMetaData = this._getModuleMetaData(fileName); + if (moduleMetaData) { + switch (moduleMetaData.mediaType) { + case MediaType.TypeScript: + return ts.ScriptKind.TS; + case MediaType.JavaScript: + return ts.ScriptKind.JS; + case 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; } } @@ -619,17 +646,17 @@ export class DenoCompiler resolveModuleNames( moduleNames: ModuleSpecifier[], containingFile: ContainingFile - ): ts.ResolvedModule[] { + ): Array<ts.ResolvedModuleFull | ts.ResolvedModule> { this._log("resolveModuleNames()", { moduleNames, containingFile }); return moduleNames.map(name => { - let resolvedFileName; + let moduleMetaData: ModuleMetaData; if (name === "deno") { // builtin modules are part of the runtime lib - resolvedFileName = this._resolveModuleName(LIB_RUNTIME, ASSETS); + moduleMetaData = this.resolveModule(LIB_RUNTIME, ASSETS); } else if (name === "typescript") { - resolvedFileName = this._resolveModuleName("typescript.d.ts", ASSETS); + moduleMetaData = this.resolveModule("typescript.d.ts", ASSETS); } else { - resolvedFileName = this._resolveModuleName(name, containingFile); + moduleMetaData = this.resolveModule(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 @@ -638,12 +665,15 @@ export class DenoCompiler // 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 || ""; + 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); - // TODO: we should be returning a ts.ResolveModuleFull - return { resolvedFileName, isExternalLibraryImport }; + return { + resolvedFileName, + isExternalLibraryImport, + extension: getExtension(resolvedFileName, moduleMetaData.mediaType) + }; }); } @@ -662,9 +692,7 @@ export class DenoCompiler private static _instance: DenoCompiler | undefined; - /** - * Returns the instance of `DenoCompiler` or creates a new instance. - */ + /** Returns the instance of `DenoCompiler` or creates a new instance. */ static instance(): DenoCompiler { return ( DenoCompiler._instance || (DenoCompiler._instance = new DenoCompiler()) diff --git a/js/compiler_test.ts b/js/compiler_test.ts index f05a96e52..7d1bdc150 100644 --- a/js/compiler_test.ts +++ b/js/compiler_test.ts @@ -11,6 +11,7 @@ const { DenoCompiler } = (deno as any)._compiler; interface ModuleInfo { moduleName: string | null; filename: string | null; + mediaType: MediaType | null; sourceCode: string | null; outputCode: string | null; } @@ -27,15 +28,24 @@ const originals = { _window: (compilerInstance as any)._window }; +enum MediaType { + JavaScript = 0, + TypeScript = 1, + Json = 2, + Unknown = 3 +} + function mockModuleInfo( moduleName: string | null, filename: string | null, + mediaType: MediaType | null, sourceCode: string | null, outputCode: string | null ): ModuleInfo { return { moduleName, filename, + mediaType, sourceCode, outputCode }; @@ -61,6 +71,7 @@ export class A { const modAModuleInfo = mockModuleInfo( "modA", "/root/project/modA.ts", + MediaType.TypeScript, modASource, undefined ); @@ -75,6 +86,7 @@ export class B { const modBModuleInfo = mockModuleInfo( "modB", "/root/project/modB.ts", + MediaType.TypeScript, modBSource, undefined ); @@ -107,21 +119,31 @@ const moduleMap: { "foo/bar.ts": mockModuleInfo( "/root/project/foo/bar.ts", "/root/project/foo/bar.ts", + MediaType.TypeScript, fooBarTsSource, null ), "foo/baz.ts": mockModuleInfo( "/root/project/foo/baz.ts", "/root/project/foo/baz.ts", + MediaType.TypeScript, fooBazTsSource, fooBazTsOutput ), - "modA.ts": modAModuleInfo + "modA.ts": modAModuleInfo, + "some.txt": mockModuleInfo( + "/root/project/some.txt", + "/root/project/some.text", + MediaType.Unknown, + "console.log();", + null + ) }, "/root/project/foo/baz.ts": { "./bar.ts": mockModuleInfo( "/root/project/foo/bar.ts", "/root/project/foo/bar.ts", + MediaType.TypeScript, fooBarTsSource, fooBarTsOutput ) @@ -131,6 +153,43 @@ const moduleMap: { }, "/root/project/modB.ts": { "./modA.ts": modAModuleInfo + }, + "/moduleKinds": { + "foo.ts": mockModuleInfo( + "foo", + "/moduleKinds/foo.ts", + MediaType.TypeScript, + "console.log('foo');", + undefined + ), + "foo.d.ts": mockModuleInfo( + "foo", + "/moduleKinds/foo.d.ts", + MediaType.TypeScript, + "console.log('foo');", + undefined + ), + "foo.js": mockModuleInfo( + "foo", + "/moduleKinds/foo.js", + MediaType.JavaScript, + "console.log('foo');", + undefined + ), + "foo.json": mockModuleInfo( + "foo", + "/moduleKinds/foo.json", + MediaType.Json, + "console.log('foo');", + undefined + ), + "foo.txt": mockModuleInfo( + "foo", + "/moduleKinds/foo.txt", + MediaType.JavaScript, + "console.log('foo');", + undefined + ) } }; @@ -180,6 +239,7 @@ const osMock = { moduleCache[fileName] = mockModuleInfo( fileName, fileName, + MediaType.TypeScript, sourceCode, outputCode ); @@ -192,7 +252,7 @@ const osMock = { return moduleMap[containingFile][moduleSpecifier]; } } - return mockModuleInfo(null, null, null, null); + return mockModuleInfo(null, null, null, null, null); }, exit(code: number): never { throw new Error(`os.exit(${code})`); @@ -405,6 +465,23 @@ test(function compilerResolveModule() { teardown(); }); +test(function compilerResolveModuleUnknownMediaType() { + setup(); + let didThrow = false; + try { + compilerInstance.resolveModule("some.txt", "/root/project"); + } catch (e) { + assert(e instanceof Error); + assertEqual( + e.message, + `Unknown media type for: "some.txt" from "/root/project".` + ); + didThrow = true; + } + assert(didThrow); + teardown(); +}); + test(function compilerGetModuleDependencies() { setup(); const bazDeps = ["require", "exports", "./bar.ts"]; @@ -484,11 +561,33 @@ test(function compilerRecompileFlag() { }); test(function compilerGetScriptKind() { - assertEqual(compilerInstance.getScriptKind("foo.ts"), ts.ScriptKind.TS); - assertEqual(compilerInstance.getScriptKind("foo.d.ts"), ts.ScriptKind.TS); - assertEqual(compilerInstance.getScriptKind("foo.js"), ts.ScriptKind.JS); - assertEqual(compilerInstance.getScriptKind("foo.json"), ts.ScriptKind.JSON); - assertEqual(compilerInstance.getScriptKind("foo.txt"), ts.ScriptKind.JS); + setup(); + compilerInstance.resolveModule("foo.ts", "/moduleKinds"); + compilerInstance.resolveModule("foo.d.ts", "/moduleKinds"); + compilerInstance.resolveModule("foo.js", "/moduleKinds"); + compilerInstance.resolveModule("foo.json", "/moduleKinds"); + compilerInstance.resolveModule("foo.txt", "/moduleKinds"); + assertEqual( + compilerInstance.getScriptKind("/moduleKinds/foo.ts"), + ts.ScriptKind.TS + ); + assertEqual( + compilerInstance.getScriptKind("/moduleKinds/foo.d.ts"), + ts.ScriptKind.TS + ); + assertEqual( + compilerInstance.getScriptKind("/moduleKinds/foo.js"), + ts.ScriptKind.JS + ); + assertEqual( + compilerInstance.getScriptKind("/moduleKinds/foo.json"), + ts.ScriptKind.JSON + ); + assertEqual( + compilerInstance.getScriptKind("/moduleKinds/foo.txt"), + ts.ScriptKind.JS + ); + teardown(); }); test(function compilerGetScriptVersion() { @@ -1,11 +1,18 @@ // Copyright 2018 the Deno authors. All rights reserved. MIT license. -import { ModuleInfo } from "./types"; import * as msg from "gen/msg_generated"; import { assert } from "./util"; import * as util from "./util"; import * as flatbuffers from "./flatbuffers"; import { sendSync } from "./dispatch"; +interface CodeInfo { + moduleName: string | undefined; + filename: string | undefined; + mediaType: msg.MediaType; + sourceCode: string | undefined; + outputCode: string | undefined; +} + /** Exit the Deno process with optional exit code. */ export function exit(exitCode = 0): never { const builder = flatbuffers.createBuilder(); @@ -20,7 +27,7 @@ export function exit(exitCode = 0): never { export function codeFetch( moduleSpecifier: string, containingFile: string -): ModuleInfo { +): CodeInfo { util.log("os.ts codeFetch", moduleSpecifier, containingFile); // Send CodeFetch message const builder = flatbuffers.createBuilder(); @@ -38,11 +45,14 @@ export function codeFetch( ); const codeFetchRes = new msg.CodeFetchRes(); assert(baseRes!.inner(codeFetchRes) != null); + // 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: codeFetchRes.moduleName(), - filename: codeFetchRes.filename(), - sourceCode: codeFetchRes.sourceCode(), - outputCode: codeFetchRes.outputCode() + moduleName: codeFetchRes.moduleName() || undefined, + filename: codeFetchRes.filename() || undefined, + mediaType: codeFetchRes.mediaType(), + sourceCode: codeFetchRes.sourceCode() || undefined, + outputCode: codeFetchRes.outputCode() || undefined }; } diff --git a/js/types.ts b/js/types.ts index 4e35d1227..954816811 100644 --- a/js/types.ts +++ b/js/types.ts @@ -1,14 +1,6 @@ // Copyright 2018 the Deno authors. All rights reserved. MIT license. export type TypedArray = Uint8Array | Float32Array | Int32Array; -// @internal -export interface ModuleInfo { - moduleName: string | null; - filename: string | null; - sourceCode: string | null; - outputCode: string | null; -} - // tslint:disable:max-line-length // Following definitions adapted from: // https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/node/index.d.ts |