summaryrefslogtreecommitdiff
path: root/cli/js/compiler/sourcefile.ts
diff options
context:
space:
mode:
Diffstat (limited to 'cli/js/compiler/sourcefile.ts')
-rw-r--r--cli/js/compiler/sourcefile.ts189
1 files changed, 189 insertions, 0 deletions
diff --git a/cli/js/compiler/sourcefile.ts b/cli/js/compiler/sourcefile.ts
new file mode 100644
index 000000000..cfa09cde3
--- /dev/null
+++ b/cli/js/compiler/sourcefile.ts
@@ -0,0 +1,189 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+import { getMappedModuleName, parseTypeDirectives } from "./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;
+}
+
+export const ASSETS = "$asset$";
+
+/** 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(processJsImports: boolean): 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: Array<{ fileName: string }>): 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 if (
+ !(
+ !processJsImports &&
+ (this.mediaType === MediaType.JavaScript ||
+ this.mediaType === MediaType.JSX)
+ )
+ ) {
+ process(importedFiles);
+ }
+ process(referencedFiles);
+ // built in libs comes across as `"dom"` for example, and should be filtered
+ // out during pre-processing as they are either already cached or they will
+ // be lazily fetched by the compiler host. Ones that contain full files are
+ // not filtered out and will be fetched as normal.
+ process(
+ libReferenceDirectives.filter(
+ ({ fileName }) => !ts.libMap.has(fileName.toLowerCase())
+ )
+ );
+ 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);
+ }
+}