mirror of
https://github.com/denoland/deno.git
synced 2024-10-31 09:14:20 -04:00
169 lines
5.1 KiB
TypeScript
169 lines
5.1 KiB
TypeScript
|
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||
|
|
||
|
import {
|
||
|
getMappedModuleName,
|
||
|
parseTypeDirectives
|
||
|
} from "./compiler_type_directives.ts";
|
||
|
import { assert, log } from "./util.ts";
|
||
|
|
||
|
// Warning! The values in this enum are duplicated in `cli/msg.rs`
|
||
|
// Update carefully!
|
||
|
export enum MediaType {
|
||
|
JavaScript = 0,
|
||
|
JSX = 1,
|
||
|
TypeScript = 2,
|
||
|
TSX = 3,
|
||
|
Json = 4,
|
||
|
Wasm = 5,
|
||
|
Unknown = 6
|
||
|
}
|
||
|
|
||
|
/** The shape of the SourceFile that comes from the privileged side */
|
||
|
export interface SourceFileJson {
|
||
|
url: string;
|
||
|
filename: string;
|
||
|
mediaType: MediaType;
|
||
|
sourceCode: string;
|
||
|
}
|
||
|
|
||
|
/** Returns the TypeScript Extension enum for a given media type. */
|
||
|
function getExtension(fileName: string, mediaType: MediaType): ts.Extension {
|
||
|
switch (mediaType) {
|
||
|
case MediaType.JavaScript:
|
||
|
return ts.Extension.Js;
|
||
|
case MediaType.JSX:
|
||
|
return ts.Extension.Jsx;
|
||
|
case MediaType.TypeScript:
|
||
|
return fileName.endsWith(".d.ts") ? ts.Extension.Dts : ts.Extension.Ts;
|
||
|
case MediaType.TSX:
|
||
|
return ts.Extension.Tsx;
|
||
|
case MediaType.Json:
|
||
|
return ts.Extension.Json;
|
||
|
case MediaType.Wasm:
|
||
|
// Custom marker for Wasm type.
|
||
|
return ts.Extension.Js;
|
||
|
case MediaType.Unknown:
|
||
|
default:
|
||
|
throw TypeError("Cannot resolve extension.");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** A self registering abstraction of source files. */
|
||
|
export class SourceFile {
|
||
|
extension!: ts.Extension;
|
||
|
filename!: string;
|
||
|
|
||
|
/** An array of tuples which represent the imports for the source file. The
|
||
|
* first element is the one that will be requested at compile time, the
|
||
|
* second is the one that should be actually resolved. This provides the
|
||
|
* feature of type directives for Deno. */
|
||
|
importedFiles?: Array<[string, string]>;
|
||
|
|
||
|
mediaType!: MediaType;
|
||
|
processed = false;
|
||
|
sourceCode!: string;
|
||
|
tsSourceFile?: ts.SourceFile;
|
||
|
url!: string;
|
||
|
|
||
|
constructor(json: SourceFileJson) {
|
||
|
if (SourceFile._moduleCache.has(json.url)) {
|
||
|
throw new TypeError("SourceFile already exists");
|
||
|
}
|
||
|
Object.assign(this, json);
|
||
|
this.extension = getExtension(this.url, this.mediaType);
|
||
|
SourceFile._moduleCache.set(this.url, this);
|
||
|
}
|
||
|
|
||
|
/** Cache the source file to be able to be retrieved by `moduleSpecifier` and
|
||
|
* `containingFile`. */
|
||
|
cache(moduleSpecifier: string, containingFile?: string): void {
|
||
|
containingFile = containingFile || "";
|
||
|
let innerCache = SourceFile._specifierCache.get(containingFile);
|
||
|
if (!innerCache) {
|
||
|
innerCache = new Map();
|
||
|
SourceFile._specifierCache.set(containingFile, innerCache);
|
||
|
}
|
||
|
innerCache.set(moduleSpecifier, this);
|
||
|
}
|
||
|
|
||
|
/** Process the imports for the file and return them. */
|
||
|
imports(): Array<[string, string]> {
|
||
|
if (this.processed) {
|
||
|
throw new Error("SourceFile has already been processed.");
|
||
|
}
|
||
|
assert(this.sourceCode != null);
|
||
|
// we shouldn't process imports for files which contain the nocheck pragma
|
||
|
// (like bundles)
|
||
|
if (this.sourceCode.match(/\/{2}\s+@ts-nocheck/)) {
|
||
|
log(`Skipping imports for "${this.filename}"`);
|
||
|
return [];
|
||
|
}
|
||
|
const preProcessedFileInfo = ts.preProcessFile(this.sourceCode, true, true);
|
||
|
this.processed = true;
|
||
|
const files = (this.importedFiles = [] as Array<[string, string]>);
|
||
|
|
||
|
function process(references: ts.FileReference[]): void {
|
||
|
for (const { fileName } of references) {
|
||
|
files.push([fileName, fileName]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const {
|
||
|
importedFiles,
|
||
|
referencedFiles,
|
||
|
libReferenceDirectives,
|
||
|
typeReferenceDirectives
|
||
|
} = preProcessedFileInfo;
|
||
|
const typeDirectives = parseTypeDirectives(this.sourceCode);
|
||
|
if (typeDirectives) {
|
||
|
for (const importedFile of importedFiles) {
|
||
|
files.push([
|
||
|
importedFile.fileName,
|
||
|
getMappedModuleName(importedFile, typeDirectives)
|
||
|
]);
|
||
|
}
|
||
|
} else {
|
||
|
process(importedFiles);
|
||
|
}
|
||
|
process(referencedFiles);
|
||
|
process(libReferenceDirectives);
|
||
|
process(typeReferenceDirectives);
|
||
|
return files;
|
||
|
}
|
||
|
|
||
|
/** A cache of all the source files which have been loaded indexed by the
|
||
|
* url. */
|
||
|
private static _moduleCache: Map<string, SourceFile> = new Map();
|
||
|
|
||
|
/** A cache of source files based on module specifiers and containing files
|
||
|
* which is used by the TypeScript compiler to resolve the url */
|
||
|
private static _specifierCache: Map<
|
||
|
string,
|
||
|
Map<string, SourceFile>
|
||
|
> = new Map();
|
||
|
|
||
|
/** Retrieve a `SourceFile` based on a `moduleSpecifier` and `containingFile`
|
||
|
* or return `undefined` if not preset. */
|
||
|
static getUrl(
|
||
|
moduleSpecifier: string,
|
||
|
containingFile: string
|
||
|
): string | undefined {
|
||
|
const containingCache = this._specifierCache.get(containingFile);
|
||
|
if (containingCache) {
|
||
|
const sourceFile = containingCache.get(moduleSpecifier);
|
||
|
return sourceFile && sourceFile.url;
|
||
|
}
|
||
|
return undefined;
|
||
|
}
|
||
|
|
||
|
/** Retrieve a `SourceFile` based on a `url` */
|
||
|
static get(url: string): SourceFile | undefined {
|
||
|
return this._moduleCache.get(url);
|
||
|
}
|
||
|
|
||
|
/** Determine if a source file exists or not */
|
||
|
static has(url: string): boolean {
|
||
|
return this._moduleCache.has(url);
|
||
|
}
|
||
|
}
|