0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-03-03 17:34:47 -05:00

Enforce media types

This commit is contained in:
Kitson Kelly 2018-10-22 13:14:27 +11:00 committed by Ryan Dahl
parent c0492ef061
commit 8ef7da2611
22 changed files with 472 additions and 83 deletions

View file

@ -1,5 +1,6 @@
// Copyright 2018 the Deno authors. All rights reserved. MIT license. // Copyright 2018 the Deno authors. All rights reserved. MIT license.
import * as ts from "typescript"; import * as ts from "typescript";
import { MediaType } from "gen/msg_generated";
import { assetSourceCode } from "./assets"; import { assetSourceCode } from "./assets";
// tslint:disable-next-line:no-circular-imports // tslint:disable-next-line:no-circular-imports
import * as deno from "./deno"; import * as deno from "./deno";
@ -85,6 +86,7 @@ export class ModuleMetaData implements ts.IScriptSnapshot {
constructor( constructor(
public readonly moduleId: ModuleId, public readonly moduleId: ModuleId,
public readonly fileName: ModuleFileName, public readonly fileName: ModuleFileName,
public readonly mediaType: MediaType,
public readonly sourceCode: SourceCode = "", public readonly sourceCode: SourceCode = "",
public outputCode: OutputCode = "" 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 /** A singleton class that combines the TypeScript Language Service host API
* with Deno specific APIs to provide an interface for compiling and running * with Deno specific APIs to provide an interface for compiling and running
* TypeScript and JavaScript modules. * TypeScript and JavaScript modules.
@ -319,17 +338,6 @@ export class DenoCompiler
return undefined; 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` /** Caches the resolved `fileName` in relationship to the `moduleSpecifier`
* and `containingFile` in order to reduce calls to the privileged side * and `containingFile` in order to reduce calls to the privileged side
* to retrieve the contents of a module. * to retrieve the contents of a module.
@ -479,9 +487,10 @@ export class DenoCompiler
if (fileName && this._moduleMetaDataMap.has(fileName)) { if (fileName && this._moduleMetaDataMap.has(fileName)) {
return this._moduleMetaDataMap.get(fileName)!; return this._moduleMetaDataMap.get(fileName)!;
} }
let moduleId: ModuleId; let moduleId: ModuleId | undefined;
let sourceCode: SourceCode; let mediaType = MediaType.Unknown;
let outputCode: OutputCode | null; let sourceCode: SourceCode | undefined;
let outputCode: OutputCode | undefined;
if ( if (
moduleSpecifier.startsWith(ASSETS) || moduleSpecifier.startsWith(ASSETS) ||
containingFile.startsWith(ASSETS) containingFile.startsWith(ASSETS)
@ -492,6 +501,7 @@ export class DenoCompiler
moduleId = moduleSpecifier.split("/").pop()!; moduleId = moduleSpecifier.split("/").pop()!;
const assetName = moduleId.includes(".") ? moduleId : `${moduleId}.d.ts`; const assetName = moduleId.includes(".") ? moduleId : `${moduleId}.d.ts`;
assert(assetName in assetSourceCode, `No such asset "${assetName}"`); assert(assetName in assetSourceCode, `No such asset "${assetName}"`);
mediaType = MediaType.TypeScript;
sourceCode = assetSourceCode[assetName]; sourceCode = assetSourceCode[assetName];
fileName = `${ASSETS}/${assetName}`; fileName = `${ASSETS}/${assetName}`;
outputCode = ""; outputCode = "";
@ -499,25 +509,38 @@ export class DenoCompiler
// We query Rust with a CodeFetch message. It will load the sourceCode, // We query Rust with a CodeFetch message. It will load the sourceCode,
// and if there is any outputCode cached, will return that as well. // and if there is any outputCode cached, will return that as well.
const fetchResponse = this._os.codeFetch(moduleSpecifier, containingFile); const fetchResponse = this._os.codeFetch(moduleSpecifier, containingFile);
moduleId = fetchResponse.moduleName!; moduleId = fetchResponse.moduleName;
fileName = fetchResponse.filename!; fileName = fetchResponse.filename;
sourceCode = fetchResponse.sourceCode!; mediaType = fetchResponse.mediaType;
outputCode = fetchResponse.outputCode!; sourceCode = fetchResponse.sourceCode;
outputCode = fetchResponse.outputCode;
} }
assert(sourceCode!.length > 0); assert(moduleId != null, "No module ID.");
this._log("resolveModule sourceCode length:", sourceCode.length); assert(fileName != null, "No file name.");
this._log("resolveModule has outputCode:", outputCode! != null); assert(sourceCode ? sourceCode.length > 0 : false, "No source code.");
this._setFileName(moduleSpecifier, containingFile, fileName); 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)) { if (fileName && this._moduleMetaDataMap.has(fileName)) {
return this._moduleMetaDataMap.get(fileName)!; return this._moduleMetaDataMap.get(fileName)!;
} }
const moduleMetaData = new ModuleMetaData( const moduleMetaData = new ModuleMetaData(
moduleId, moduleId!,
fileName, fileName!,
mediaType,
sourceCode, sourceCode,
outputCode outputCode
); );
this._moduleMetaDataMap.set(fileName, moduleMetaData); this._moduleMetaDataMap.set(fileName!, moduleMetaData);
return moduleMetaData; return moduleMetaData;
} }
@ -563,17 +586,21 @@ export class DenoCompiler
getScriptKind(fileName: ModuleFileName): ts.ScriptKind { getScriptKind(fileName: ModuleFileName): ts.ScriptKind {
this._log("getScriptKind()", fileName); this._log("getScriptKind()", fileName);
const suffix = fileName.substr(fileName.lastIndexOf(".") + 1); const moduleMetaData = this._getModuleMetaData(fileName);
switch (suffix) { if (moduleMetaData) {
case "ts": switch (moduleMetaData.mediaType) {
case MediaType.TypeScript:
return ts.ScriptKind.TS; return ts.ScriptKind.TS;
case "js": case MediaType.JavaScript:
return ts.ScriptKind.JS; return ts.ScriptKind.JS;
case "json": case MediaType.Json:
return ts.ScriptKind.JSON; return ts.ScriptKind.JSON;
default: default:
return this._options.allowJs ? ts.ScriptKind.JS : ts.ScriptKind.TS; return this._options.allowJs ? ts.ScriptKind.JS : ts.ScriptKind.TS;
} }
} else {
return this._options.allowJs ? ts.ScriptKind.JS : ts.ScriptKind.TS;
}
} }
getScriptVersion(fileName: ModuleFileName): string { getScriptVersion(fileName: ModuleFileName): string {
@ -619,17 +646,17 @@ export class DenoCompiler
resolveModuleNames( resolveModuleNames(
moduleNames: ModuleSpecifier[], moduleNames: ModuleSpecifier[],
containingFile: ContainingFile containingFile: ContainingFile
): ts.ResolvedModule[] { ): Array<ts.ResolvedModuleFull | ts.ResolvedModule> {
this._log("resolveModuleNames()", { moduleNames, containingFile }); this._log("resolveModuleNames()", { moduleNames, containingFile });
return moduleNames.map(name => { return moduleNames.map(name => {
let resolvedFileName; let moduleMetaData: ModuleMetaData;
if (name === "deno") { if (name === "deno") {
// builtin modules are part of the runtime lib // builtin modules are part of the runtime lib
resolvedFileName = this._resolveModuleName(LIB_RUNTIME, ASSETS); moduleMetaData = this.resolveModule(LIB_RUNTIME, ASSETS);
} else if (name === "typescript") { } else if (name === "typescript") {
resolvedFileName = this._resolveModuleName("typescript.d.ts", ASSETS); moduleMetaData = this.resolveModule("typescript.d.ts", ASSETS);
} else { } else {
resolvedFileName = this._resolveModuleName(name, containingFile); moduleMetaData = this.resolveModule(name, containingFile);
} }
// According to the interface we shouldn't return `undefined` but if we // 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 // 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 // TODO: all this does is push the problem downstream, and TypeScript
// will complain it can't identify the type of the file and throw // will complain it can't identify the type of the file and throw
// a runtime exception, so we need to handle missing modules better // 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 // This flags to the compiler to not go looking to transpile functional
// code, anything that is in `/$asset$/` is just library code // code, anything that is in `/$asset$/` is just library code
const isExternalLibraryImport = resolvedFileName.startsWith(ASSETS); const isExternalLibraryImport = resolvedFileName.startsWith(ASSETS);
// TODO: we should be returning a ts.ResolveModuleFull return {
return { resolvedFileName, isExternalLibraryImport }; resolvedFileName,
isExternalLibraryImport,
extension: getExtension(resolvedFileName, moduleMetaData.mediaType)
};
}); });
} }
@ -662,9 +692,7 @@ export class DenoCompiler
private static _instance: DenoCompiler | undefined; 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 { static instance(): DenoCompiler {
return ( return (
DenoCompiler._instance || (DenoCompiler._instance = new DenoCompiler()) DenoCompiler._instance || (DenoCompiler._instance = new DenoCompiler())

View file

@ -11,6 +11,7 @@ const { DenoCompiler } = (deno as any)._compiler;
interface ModuleInfo { interface ModuleInfo {
moduleName: string | null; moduleName: string | null;
filename: string | null; filename: string | null;
mediaType: MediaType | null;
sourceCode: string | null; sourceCode: string | null;
outputCode: string | null; outputCode: string | null;
} }
@ -27,15 +28,24 @@ const originals = {
_window: (compilerInstance as any)._window _window: (compilerInstance as any)._window
}; };
enum MediaType {
JavaScript = 0,
TypeScript = 1,
Json = 2,
Unknown = 3
}
function mockModuleInfo( function mockModuleInfo(
moduleName: string | null, moduleName: string | null,
filename: string | null, filename: string | null,
mediaType: MediaType | null,
sourceCode: string | null, sourceCode: string | null,
outputCode: string | null outputCode: string | null
): ModuleInfo { ): ModuleInfo {
return { return {
moduleName, moduleName,
filename, filename,
mediaType,
sourceCode, sourceCode,
outputCode outputCode
}; };
@ -61,6 +71,7 @@ export class A {
const modAModuleInfo = mockModuleInfo( const modAModuleInfo = mockModuleInfo(
"modA", "modA",
"/root/project/modA.ts", "/root/project/modA.ts",
MediaType.TypeScript,
modASource, modASource,
undefined undefined
); );
@ -75,6 +86,7 @@ export class B {
const modBModuleInfo = mockModuleInfo( const modBModuleInfo = mockModuleInfo(
"modB", "modB",
"/root/project/modB.ts", "/root/project/modB.ts",
MediaType.TypeScript,
modBSource, modBSource,
undefined undefined
); );
@ -107,21 +119,31 @@ const moduleMap: {
"foo/bar.ts": mockModuleInfo( "foo/bar.ts": mockModuleInfo(
"/root/project/foo/bar.ts", "/root/project/foo/bar.ts",
"/root/project/foo/bar.ts", "/root/project/foo/bar.ts",
MediaType.TypeScript,
fooBarTsSource, fooBarTsSource,
null null
), ),
"foo/baz.ts": mockModuleInfo( "foo/baz.ts": mockModuleInfo(
"/root/project/foo/baz.ts", "/root/project/foo/baz.ts",
"/root/project/foo/baz.ts", "/root/project/foo/baz.ts",
MediaType.TypeScript,
fooBazTsSource, fooBazTsSource,
fooBazTsOutput 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": { "/root/project/foo/baz.ts": {
"./bar.ts": mockModuleInfo( "./bar.ts": mockModuleInfo(
"/root/project/foo/bar.ts", "/root/project/foo/bar.ts",
"/root/project/foo/bar.ts", "/root/project/foo/bar.ts",
MediaType.TypeScript,
fooBarTsSource, fooBarTsSource,
fooBarTsOutput fooBarTsOutput
) )
@ -131,6 +153,43 @@ const moduleMap: {
}, },
"/root/project/modB.ts": { "/root/project/modB.ts": {
"./modA.ts": modAModuleInfo "./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( moduleCache[fileName] = mockModuleInfo(
fileName, fileName,
fileName, fileName,
MediaType.TypeScript,
sourceCode, sourceCode,
outputCode outputCode
); );
@ -192,7 +252,7 @@ const osMock = {
return moduleMap[containingFile][moduleSpecifier]; return moduleMap[containingFile][moduleSpecifier];
} }
} }
return mockModuleInfo(null, null, null, null); return mockModuleInfo(null, null, null, null, null);
}, },
exit(code: number): never { exit(code: number): never {
throw new Error(`os.exit(${code})`); throw new Error(`os.exit(${code})`);
@ -405,6 +465,23 @@ test(function compilerResolveModule() {
teardown(); 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() { test(function compilerGetModuleDependencies() {
setup(); setup();
const bazDeps = ["require", "exports", "./bar.ts"]; const bazDeps = ["require", "exports", "./bar.ts"];
@ -484,11 +561,33 @@ test(function compilerRecompileFlag() {
}); });
test(function compilerGetScriptKind() { test(function compilerGetScriptKind() {
assertEqual(compilerInstance.getScriptKind("foo.ts"), ts.ScriptKind.TS); setup();
assertEqual(compilerInstance.getScriptKind("foo.d.ts"), ts.ScriptKind.TS); compilerInstance.resolveModule("foo.ts", "/moduleKinds");
assertEqual(compilerInstance.getScriptKind("foo.js"), ts.ScriptKind.JS); compilerInstance.resolveModule("foo.d.ts", "/moduleKinds");
assertEqual(compilerInstance.getScriptKind("foo.json"), ts.ScriptKind.JSON); compilerInstance.resolveModule("foo.js", "/moduleKinds");
assertEqual(compilerInstance.getScriptKind("foo.txt"), ts.ScriptKind.JS); 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() { test(function compilerGetScriptVersion() {

View file

@ -1,11 +1,18 @@
// Copyright 2018 the Deno authors. All rights reserved. MIT license. // Copyright 2018 the Deno authors. All rights reserved. MIT license.
import { ModuleInfo } from "./types";
import * as msg from "gen/msg_generated"; import * as msg from "gen/msg_generated";
import { assert } from "./util"; import { assert } from "./util";
import * as util from "./util"; import * as util from "./util";
import * as flatbuffers from "./flatbuffers"; import * as flatbuffers from "./flatbuffers";
import { sendSync } from "./dispatch"; 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. */ /** Exit the Deno process with optional exit code. */
export function exit(exitCode = 0): never { export function exit(exitCode = 0): never {
const builder = flatbuffers.createBuilder(); const builder = flatbuffers.createBuilder();
@ -20,7 +27,7 @@ export function exit(exitCode = 0): never {
export function codeFetch( export function codeFetch(
moduleSpecifier: string, moduleSpecifier: string,
containingFile: string containingFile: string
): ModuleInfo { ): CodeInfo {
util.log("os.ts codeFetch", moduleSpecifier, containingFile); util.log("os.ts codeFetch", moduleSpecifier, containingFile);
// Send CodeFetch message // Send CodeFetch message
const builder = flatbuffers.createBuilder(); const builder = flatbuffers.createBuilder();
@ -38,11 +45,14 @@ export function codeFetch(
); );
const codeFetchRes = new msg.CodeFetchRes(); const codeFetchRes = new msg.CodeFetchRes();
assert(baseRes!.inner(codeFetchRes) != null); 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 { return {
moduleName: codeFetchRes.moduleName(), moduleName: codeFetchRes.moduleName() || undefined,
filename: codeFetchRes.filename(), filename: codeFetchRes.filename() || undefined,
sourceCode: codeFetchRes.sourceCode(), mediaType: codeFetchRes.mediaType(),
outputCode: codeFetchRes.outputCode() sourceCode: codeFetchRes.sourceCode() || undefined,
outputCode: codeFetchRes.outputCode() || undefined
}; };
} }

View file

@ -1,14 +1,6 @@
// Copyright 2018 the Deno authors. All rights reserved. MIT license. // Copyright 2018 the Deno authors. All rights reserved. MIT license.
export type TypedArray = Uint8Array | Float32Array | Int32Array; 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 // tslint:disable:max-line-length
// Following definitions adapted from: // Following definitions adapted from:
// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/node/index.d.ts // https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/node/index.d.ts

View file

@ -5,6 +5,7 @@ use errors::DenoResult;
use errors::ErrorKind; use errors::ErrorKind;
use fs as deno_fs; use fs as deno_fs;
use http_util; use http_util;
use msg;
use ring; use ring;
use std; use std;
use std::fmt::Write; use std::fmt::Write;
@ -109,21 +110,28 @@ impl DenoDir {
self: &DenoDir, self: &DenoDir,
module_name: &str, module_name: &str,
filename: &str, filename: &str,
) -> DenoResult<String> { ) -> DenoResult<(String, msg::MediaType)> {
let p = Path::new(filename); let p = Path::new(filename);
// We write a special ".mime" file into the `.deno/deps` directory along side the
// cached file, containing just the media type.
let mut media_type_filename = filename.to_string();
media_type_filename.push_str(".mime");
let mt = Path::new(&media_type_filename);
let src = if self.reload || !p.exists() { let src = if self.reload || !p.exists() {
println!("Downloading {}", module_name); println!("Downloading {}", module_name);
let source = http_util::fetch_sync_string(module_name)?; let (source, content_type) = http_util::fetch_sync_string(module_name)?;
match p.parent() { match p.parent() {
Some(ref parent) => fs::create_dir_all(parent), Some(ref parent) => fs::create_dir_all(parent),
None => Ok(()), None => Ok(()),
}?; }?;
deno_fs::write_file(&p, source.as_bytes(), 0o666)?; deno_fs::write_file(&p, source.as_bytes(), 0o666)?;
source deno_fs::write_file(&mt, content_type.as_bytes(), 0o666)?;
(source, map_content_type(&p, Some(&content_type)))
} else { } else {
let source = fs::read_to_string(&p)?; let source = fs::read_to_string(&p)?;
source let content_type = fs::read_to_string(&mt)?;
(source, map_content_type(&p, Some(&content_type)))
}; };
Ok(src) Ok(src)
} }
@ -141,18 +149,20 @@ impl DenoDir {
let use_extension = |ext| { let use_extension = |ext| {
let module_name = format!("{}{}", module_name, ext); let module_name = format!("{}{}", module_name, ext);
let filename = format!("{}{}", filename, ext); let filename = format!("{}{}", filename, ext);
let source_code = if is_module_remote { let (source_code, media_type) = if is_module_remote {
self.fetch_remote_source(&module_name, &filename)? self.fetch_remote_source(&module_name, &filename)?
} else { } else {
assert_eq!( assert_eq!(
module_name, filename, module_name, filename,
"if a module isn't remote, it should have the same filename" "if a module isn't remote, it should have the same filename"
); );
fs::read_to_string(Path::new(&filename))? let path = Path::new(&filename);
(fs::read_to_string(path)?, map_content_type(path, None))
}; };
return Ok(CodeFetchOutput { return Ok(CodeFetchOutput {
module_name: module_name.to_string(), module_name: module_name.to_string(),
filename: filename.to_string(), filename: filename.to_string(),
media_type,
source_code, source_code,
maybe_output_code: None, maybe_output_code: None,
}); });
@ -215,6 +225,7 @@ impl DenoDir {
Ok(output_code) => Ok(CodeFetchOutput { Ok(output_code) => Ok(CodeFetchOutput {
module_name: out.module_name, module_name: out.module_name,
filename: out.filename, filename: out.filename,
media_type: out.media_type,
source_code: out.source_code, source_code: out.source_code,
maybe_output_code: Some(output_code), maybe_output_code: Some(output_code),
}), }),
@ -324,6 +335,7 @@ fn test_get_cache_filename() {
pub struct CodeFetchOutput { pub struct CodeFetchOutput {
pub module_name: String, pub module_name: String,
pub filename: String, pub filename: String,
pub media_type: msg::MediaType,
pub source_code: String, pub source_code: String,
pub maybe_output_code: Option<String>, pub maybe_output_code: Option<String>,
} }
@ -646,3 +658,157 @@ fn parse_local_or_remote(p: &str) -> Result<url::Url, url::ParseError> {
Url::from_file_path(p).map_err(|_err| url::ParseError::IdnaError) Url::from_file_path(p).map_err(|_err| url::ParseError::IdnaError)
} }
} }
fn map_file_extension(path: &Path) -> msg::MediaType {
match path.extension() {
None => msg::MediaType::Unknown,
Some(os_str) => match os_str.to_str() {
Some("ts") => msg::MediaType::TypeScript,
Some("js") => msg::MediaType::JavaScript,
Some("json") => msg::MediaType::Json,
_ => msg::MediaType::Unknown,
},
}
}
#[test]
fn test_map_file_extension() {
assert_eq!(
map_file_extension(Path::new("foo/bar.ts")),
msg::MediaType::TypeScript
);
assert_eq!(
map_file_extension(Path::new("foo/bar.d.ts")),
msg::MediaType::TypeScript
);
assert_eq!(
map_file_extension(Path::new("foo/bar.js")),
msg::MediaType::JavaScript
);
assert_eq!(
map_file_extension(Path::new("foo/bar.json")),
msg::MediaType::Json
);
assert_eq!(
map_file_extension(Path::new("foo/bar.txt")),
msg::MediaType::Unknown
);
assert_eq!(
map_file_extension(Path::new("foo/bar")),
msg::MediaType::Unknown
);
}
// convert a ContentType string into a enumerated MediaType
fn map_content_type(path: &Path, content_type: Option<&str>) -> msg::MediaType {
match content_type {
Some(content_type) => {
// sometimes there is additional data after the media type in
// Content-Type so we have to do a bit of manipulation so we are only
// dealing with the actual media type
let ct_vector: Vec<&str> = content_type.split(";").collect();
let ct: &str = ct_vector.first().unwrap();
match ct.to_lowercase().as_ref() {
"application/typescript"
| "text/typescript"
| "video/vnd.dlna.mpeg-tts"
| "video/mp2t" => msg::MediaType::TypeScript,
"application/javascript"
| "text/javascript"
| "application/ecmascript"
| "text/ecmascript"
| "application/x-javascript" => msg::MediaType::JavaScript,
"application/json" | "text/json" => msg::MediaType::Json,
"text/plain" => map_file_extension(path),
_ => {
debug!("unknown content type: {}", content_type);
msg::MediaType::Unknown
}
}
}
None => map_file_extension(path),
}
}
#[test]
fn test_map_content_type() {
// Extension only
assert_eq!(
map_content_type(Path::new("foo/bar.ts"), None),
msg::MediaType::TypeScript
);
assert_eq!(
map_content_type(Path::new("foo/bar.d.ts"), None),
msg::MediaType::TypeScript
);
assert_eq!(
map_content_type(Path::new("foo/bar.js"), None),
msg::MediaType::JavaScript
);
assert_eq!(
map_content_type(Path::new("foo/bar.json"), None),
msg::MediaType::Json
);
assert_eq!(
map_content_type(Path::new("foo/bar.txt"), None),
msg::MediaType::Unknown
);
assert_eq!(
map_content_type(Path::new("foo/bar"), None),
msg::MediaType::Unknown
);
// Media Type
assert_eq!(
map_content_type(Path::new("foo/bar"), Some("application/typescript")),
msg::MediaType::TypeScript
);
assert_eq!(
map_content_type(Path::new("foo/bar"), Some("text/typescript")),
msg::MediaType::TypeScript
);
assert_eq!(
map_content_type(Path::new("foo/bar"), Some("video/vnd.dlna.mpeg-tts")),
msg::MediaType::TypeScript
);
assert_eq!(
map_content_type(Path::new("foo/bar"), Some("video/mp2t")),
msg::MediaType::TypeScript
);
assert_eq!(
map_content_type(Path::new("foo/bar"), Some("application/javascript")),
msg::MediaType::JavaScript
);
assert_eq!(
map_content_type(Path::new("foo/bar"), Some("text/javascript")),
msg::MediaType::JavaScript
);
assert_eq!(
map_content_type(Path::new("foo/bar"), Some("application/ecmascript")),
msg::MediaType::JavaScript
);
assert_eq!(
map_content_type(Path::new("foo/bar"), Some("text/ecmascript")),
msg::MediaType::JavaScript
);
assert_eq!(
map_content_type(Path::new("foo/bar"), Some("application/x-javascript")),
msg::MediaType::JavaScript
);
assert_eq!(
map_content_type(Path::new("foo/bar"), Some("application/json")),
msg::MediaType::Json
);
assert_eq!(
map_content_type(Path::new("foo/bar"), Some("text/json")),
msg::MediaType::Json
);
assert_eq!(
map_content_type(Path::new("foo/bar.ts"), Some("text/plain")),
msg::MediaType::TypeScript
);
assert_eq!(
map_content_type(Path::new("foo/bar.ts"), Some("foo/bar")),
msg::MediaType::Unknown
);
}

View file

@ -4,9 +4,10 @@ use errors::{DenoError, DenoResult};
use tokio_util; use tokio_util;
use futures::future::{loop_fn, Loop}; use futures::future::{loop_fn, Loop};
use futures::{Future, Stream}; use futures::{future, Future, Stream};
use hyper; use hyper;
use hyper::client::{Client, HttpConnector}; use hyper::client::{Client, HttpConnector};
use hyper::header::CONTENT_TYPE;
use hyper::Uri; use hyper::Uri;
use hyper_rustls; use hyper_rustls;
@ -27,7 +28,7 @@ pub fn get_client() -> Client<Connector, hyper::Body> {
// The CodeFetch message is used to load HTTP javascript resources and expects a // The CodeFetch message is used to load HTTP javascript resources and expects a
// synchronous response, this utility method supports that. // synchronous response, this utility method supports that.
pub fn fetch_sync_string(module_name: &str) -> DenoResult<String> { pub fn fetch_sync_string(module_name: &str) -> DenoResult<(String, String)> {
let url = module_name.parse::<Uri>().unwrap(); let url = module_name.parse::<Uri>().unwrap();
let client = get_client(); let client = get_client();
// TODO(kevinkassimo): consider set a max redirection counter // TODO(kevinkassimo): consider set a max redirection counter
@ -63,11 +64,18 @@ pub fn fetch_sync_string(module_name: &str) -> DenoResult<String> {
Ok(Loop::Break(response)) Ok(Loop::Break(response))
}) })
}).and_then(|response| { }).and_then(|response| {
response let content_type = response
.headers()
.get(CONTENT_TYPE)
.map(|content_type| content_type.to_str().unwrap().to_string());
let body = response
.into_body() .into_body()
.concat2() .concat2()
.map(|body| String::from_utf8(body.to_vec()).unwrap()) .map(|body| String::from_utf8(body.to_vec()).unwrap())
.map_err(|err| DenoError::from(err)) .map_err(|err| DenoError::from(err));
body.join(future::ok(content_type))
}).and_then(|(body_string, maybe_content_type)| {
future::ok((body_string, maybe_content_type.unwrap()))
}); });
tokio_util::block_on(fetch_future) tokio_util::block_on(fetch_future)
@ -77,9 +85,11 @@ pub fn fetch_sync_string(module_name: &str) -> DenoResult<String> {
fn test_fetch_sync_string() { fn test_fetch_sync_string() {
// Relies on external http server. See tools/http_server.py // Relies on external http server. See tools/http_server.py
tokio_util::init(|| { tokio_util::init(|| {
let p = fetch_sync_string("http://127.0.0.1:4545/package.json").unwrap(); let (p, m) =
fetch_sync_string("http://127.0.0.1:4545/package.json").unwrap();
println!("package.json len {}", p.len()); println!("package.json len {}", p.len());
assert!(p.len() > 1); assert!(p.len() > 1);
assert!(m == "application/json")
}); });
} }
@ -87,8 +97,10 @@ fn test_fetch_sync_string() {
fn test_fetch_sync_string_with_redirect() { fn test_fetch_sync_string_with_redirect() {
// Relies on external http server. See tools/http_server.py // Relies on external http server. See tools/http_server.py
tokio_util::init(|| { tokio_util::init(|| {
let p = fetch_sync_string("http://127.0.0.1:4546/package.json").unwrap(); let (p, m) =
fetch_sync_string("http://127.0.0.1:4546/package.json").unwrap();
println!("package.json len {}", p.len()); println!("package.json len {}", p.len());
assert!(p.len() > 1); assert!(p.len() > 1);
assert!(m == "application/json")
}); });
} }

View file

@ -102,6 +102,13 @@ table CwdRes {
cwd: string; cwd: string;
} }
enum MediaType: byte {
JavaScript = 0,
TypeScript,
Json,
Unknown
}
table Base { table Base {
cmd_id: uint32; cmd_id: uint32;
sync: bool = true; // TODO(ry) Change default to false. sync: bool = true; // TODO(ry) Change default to false.
@ -137,6 +144,7 @@ table CodeFetchRes {
// is the location of the locally downloaded source code. // is the location of the locally downloaded source code.
module_name: string; module_name: string;
filename: string; filename: string;
media_type: MediaType;
source_code: string; source_code: string;
output_code: string; // Non-empty only if cached. output_code: string; // Non-empty only if cached.
} }

View file

@ -253,6 +253,7 @@ fn op_code_fetch(
let mut msg_args = msg::CodeFetchResArgs { let mut msg_args = msg::CodeFetchResArgs {
module_name: Some(builder.create_string(&out.module_name)), module_name: Some(builder.create_string(&out.module_name)),
filename: Some(builder.create_string(&out.filename)), filename: Some(builder.create_string(&out.filename)),
media_type: out.media_type,
source_code: Some(builder.create_string(&out.source_code)), source_code: Some(builder.create_string(&out.source_code)),
..Default::default() ..Default::default()
}; };

23
tests/019_media_types.ts Normal file
View file

@ -0,0 +1,23 @@
// When run against the test HTTP server, it will serve different media types
// based on the URL containing `.t#.` strings, which exercises the different
// mapping of media types end to end.
// tslint:disable:max-line-length
import { loaded as loadedTs1 } from "http://localhost:4545/tests/subdir/mt_text_typescript.t1.ts";
import { loaded as loadedTs2 } from "http://localhost:4545/tests/subdir/mt_video_vdn.t2.ts";
import { loaded as loadedTs3 } from "http://localhost:4545/tests/subdir/mt_video_mp2t.t3.ts";
import { loaded as loadedJs1 } from "http://localhost:4545/tests/subdir/mt_text_javascript.j1.js";
import { loaded as loadedJs2 } from "http://localhost:4545/tests/subdir/mt_application_ecmascript.j2.js";
import { loaded as loadedJs3 } from "http://localhost:4545/tests/subdir/mt_text_ecmascript.j3.js";
import { loaded as loadedJs4 } from "http://localhost:4545/tests/subdir/mt_application_x_javascript.j4.js";
console.log(
"success",
loadedTs1,
loadedTs2,
loadedTs3,
loadedJs1,
loadedJs2,
loadedJs3,
loadedJs4
);

View file

@ -0,0 +1,8 @@
Downloading http://localhost:4545/tests/subdir/mt_text_typescript.t1.ts
Downloading http://localhost:4545/tests/subdir/mt_video_vdn.t2.ts
Downloading http://localhost:4545/tests/subdir/mt_video_mp2t.t3.ts
Downloading http://localhost:4545/tests/subdir/mt_text_javascript.j1.js
Downloading http://localhost:4545/tests/subdir/mt_application_ecmascript.j2.js
Downloading http://localhost:4545/tests/subdir/mt_text_ecmascript.j3.js
Downloading http://localhost:4545/tests/subdir/mt_application_x_javascript.j4.js
success true true true true true true true

View file

@ -4,8 +4,8 @@ NotFound: Cannot resolve module "bad-module.ts" from "[WILDCARD]/tests/error_004
at sendSync ([WILDCARD]/js/dispatch.ts:[WILDCARD]) at sendSync ([WILDCARD]/js/dispatch.ts:[WILDCARD])
at Object.codeFetch ([WILDCARD]/js/os.ts:[WILDCARD]) at Object.codeFetch ([WILDCARD]/js/os.ts:[WILDCARD])
at DenoCompiler.resolveModule ([WILDCARD]/js/compiler.ts:[WILDCARD]) at DenoCompiler.resolveModule ([WILDCARD]/js/compiler.ts:[WILDCARD])
at DenoCompiler._resolveModuleName ([WILDCARD]/js/compiler.ts:[WILDCARD])
at moduleNames.map.name ([WILDCARD]/js/compiler.ts:[WILDCARD]) at moduleNames.map.name ([WILDCARD]/js/compiler.ts:[WILDCARD])
at Array.map (<anonymous>) at Array.map (<anonymous>)
at DenoCompiler.resolveModuleNames ([WILDCARD]/js/compiler.ts:[WILDCARD]) at DenoCompiler.resolveModuleNames ([WILDCARD]/js/compiler.ts:[WILDCARD])
at Object.compilerHost.resolveModuleNames ([WILDCARD]/third_party/node_modules/typescript/lib/typescript.js:[WILDCARD]) at Object.compilerHost.resolveModuleNames ([WILDCARD]/third_party/node_modules/typescript/lib/typescript.js:[WILDCARD])
at resolveModuleNamesWorker ([WILDCARD]/third_party/node_modules/typescript/lib/typescript.js:[WILDCARD])

View file

@ -4,8 +4,8 @@ NotFound: Cannot resolve module "bad-module.ts" from "[WILDCARD]/tests/error_005
at sendSync ([WILDCARD]/js/dispatch.ts:[WILDCARD]) at sendSync ([WILDCARD]/js/dispatch.ts:[WILDCARD])
at Object.codeFetch ([WILDCARD]/js/os.ts:[WILDCARD]) at Object.codeFetch ([WILDCARD]/js/os.ts:[WILDCARD])
at DenoCompiler.resolveModule ([WILDCARD]/js/compiler.ts:[WILDCARD]) at DenoCompiler.resolveModule ([WILDCARD]/js/compiler.ts:[WILDCARD])
at DenoCompiler._resolveModuleName ([WILDCARD]/js/compiler.ts:[WILDCARD])
at moduleNames.map.name ([WILDCARD]/js/compiler.ts:[WILDCARD]) at moduleNames.map.name ([WILDCARD]/js/compiler.ts:[WILDCARD])
at Array.map (<anonymous>) at Array.map (<anonymous>)
at DenoCompiler.resolveModuleNames ([WILDCARD]/js/compiler.ts:[WILDCARD]) at DenoCompiler.resolveModuleNames ([WILDCARD]/js/compiler.ts:[WILDCARD])
at Object.compilerHost.resolveModuleNames ([WILDCARD]/third_party/node_modules/typescript/lib/typescript.js:[WILDCARD]) at Object.compilerHost.resolveModuleNames ([WILDCARD]/third_party/node_modules/typescript/lib/typescript.js:[WILDCARD])
at resolveModuleNamesWorker ([WILDCARD]/third_party/node_modules/typescript/lib/typescript.js:[WILDCARD])

View file

@ -4,8 +4,8 @@ NotFound: Cannot resolve module "./non-existent" from "[WILDCARD]/tests/error_00
at sendSync ([WILDCARD]/js/dispatch.ts:[WILDCARD]) at sendSync ([WILDCARD]/js/dispatch.ts:[WILDCARD])
at Object.codeFetch ([WILDCARD]/js/os.ts:[WILDCARD]) at Object.codeFetch ([WILDCARD]/js/os.ts:[WILDCARD])
at DenoCompiler.resolveModule ([WILDCARD]/js/compiler.ts:[WILDCARD]) at DenoCompiler.resolveModule ([WILDCARD]/js/compiler.ts:[WILDCARD])
at DenoCompiler._resolveModuleName ([WILDCARD]/js/compiler.ts:[WILDCARD])
at moduleNames.map.name ([WILDCARD]/js/compiler.ts:[WILDCARD]) at moduleNames.map.name ([WILDCARD]/js/compiler.ts:[WILDCARD])
at Array.map (<anonymous>) at Array.map (<anonymous>)
at DenoCompiler.resolveModuleNames ([WILDCARD]/js/compiler.ts:[WILDCARD]) at DenoCompiler.resolveModuleNames ([WILDCARD]/js/compiler.ts:[WILDCARD])
at Object.compilerHost.resolveModuleNames ([WILDCARD]/third_party/node_modules/typescript/lib/typescript.js:[WILDCARD]) at Object.compilerHost.resolveModuleNames ([WILDCARD]/third_party/node_modules/typescript/lib/typescript.js:[WILDCARD])
at resolveModuleNamesWorker ([WILDCARD]/third_party/node_modules/typescript/lib/typescript.js:[WILDCARD])

View file

@ -0,0 +1,3 @@
define(["exports"], function(exports) {
exports.loaded = true;
});

View file

@ -0,0 +1,3 @@
define(["exports"], function(exports) {
exports.loaded = true;
});

View file

@ -0,0 +1,3 @@
define(["exports"], function(exports) {
exports.loaded = true;
});

View file

@ -0,0 +1,3 @@
define(["exports"], function(exports) {
exports.loaded = true;
});

View file

@ -0,0 +1,3 @@
define(["exports"], function(exports) {
exports.loaded = true;
});

View file

@ -0,0 +1 @@
export const loaded = true;

View file

@ -0,0 +1 @@
export const loaded = true;

View file

@ -0,0 +1 @@
export const loaded = true;

View file

@ -14,9 +14,33 @@ PORT = 4545
REDIRECT_PORT = 4546 REDIRECT_PORT = 4546
class ContentTypeHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
def guess_type(self, path):
if ".t1." in path:
return "text/typescript"
if ".t2." in path:
return "video/vnd.dlna.mpeg-tts"
if ".t3." in path:
return "video/mp2t"
if ".j1." in path:
return "text/javascript"
if ".j2." in path:
return "application/ecmascript"
if ".j3." in path:
return "text/ecmascript"
if ".j4." in path:
return "application/x-javascript"
return SimpleHTTPServer.SimpleHTTPRequestHandler.guess_type(self, path)
def server(): def server():
os.chdir(root_path) # Hopefully the main thread doesn't also chdir. os.chdir(root_path) # Hopefully the main thread doesn't also chdir.
Handler = SimpleHTTPServer.SimpleHTTPRequestHandler Handler = ContentTypeHandler
Handler.extensions_map.update({
".ts": "application/typescript",
".js": "application/javascript",
".json": "application/json",
})
SocketServer.TCPServer.allow_reuse_address = True SocketServer.TCPServer.allow_reuse_address = True
s = SocketServer.TCPServer(("", PORT), Handler) s = SocketServer.TCPServer(("", PORT), Handler)
print "Deno test server http://localhost:%d/" % PORT print "Deno test server http://localhost:%d/" % PORT