mirror of
https://github.com/denoland/deno.git
synced 2024-12-25 00:29:09 -05:00
175 lines
5.2 KiB
TypeScript
175 lines
5.2 KiB
TypeScript
// Copyright 2018-2020 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 for "${fileName}" with mediaType "${MediaType[mediaType]}".`
|
|
);
|
|
}
|
|
}
|
|
|
|
/** 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,
|
|
this.mediaType === MediaType.JavaScript ||
|
|
this.mediaType === MediaType.JSX
|
|
);
|
|
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);
|
|
}
|
|
}
|