Skip to content

Commit

Permalink
Enforce media types
Browse files Browse the repository at this point in the history
  • Loading branch information
kitsonk committed Oct 23, 2018
1 parent 64f0dfd commit e6ee66c
Show file tree
Hide file tree
Showing 22 changed files with 472 additions and 83 deletions.
120 changes: 74 additions & 46 deletions js/compiler.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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 = ""
) {
Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Expand All @@ -492,32 +501,46 @@ 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 = "";
} 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.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;
}

Expand Down Expand Up @@ -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;
}
}

Expand Down Expand Up @@ -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
Expand All @@ -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)
};
});
}

Expand All @@ -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())
Expand Down
Loading

0 comments on commit e6ee66c

Please sign in to comment.