0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-02-18 19:33:00 -05:00
denoland-deno/cli/js/compiler/host.ts
Bartek Iwańczuk cf5a39a361
refactor(ts): remove op_cache (#5071)
This PR removes op_cache and refactors how Deno interacts with TS compiler.

Ultimate goal is to completely sandbox TS compiler worker; it should operate on
simple request -> response basis. With this commit TS compiler no longer
caches compiled sources as they are generated but rather collects all sources
and sends them back to Rust when compilation is done.

Additionally "Diagnostic" and its children got refactored to use "Deserialize" trait
instead of manually implementing JSON deserialization.
2020-05-05 18:23:15 +02:00

329 lines
7.9 KiB
TypeScript

// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
import { ASSETS, MediaType, SourceFile } from "./sourcefile.ts";
import { OUT_DIR, WriteFileCallback, getAsset } from "./util.ts";
import { assert, notImplemented } from "../util.ts";
import * as util from "../util.ts";
export enum CompilerHostTarget {
Main = "main",
Runtime = "runtime",
Worker = "worker",
}
export interface CompilerHostOptions {
bundle?: boolean;
target: CompilerHostTarget;
unstable?: boolean;
writeFile: WriteFileCallback;
}
export interface ConfigureResponse {
ignoredOptions?: string[];
diagnostics?: ts.Diagnostic[];
}
export const defaultBundlerOptions: ts.CompilerOptions = {
allowJs: true,
inlineSourceMap: false,
module: ts.ModuleKind.System,
outDir: undefined,
outFile: `${OUT_DIR}/bundle.js`,
// disabled until we have effective way to modify source maps
sourceMap: false,
};
export const defaultCompileOptions: ts.CompilerOptions = {
allowJs: false,
allowNonTsExtensions: true,
checkJs: false,
esModuleInterop: true,
jsx: ts.JsxEmit.React,
module: ts.ModuleKind.ESNext,
outDir: OUT_DIR,
resolveJsonModule: true,
sourceMap: true,
strict: true,
stripComments: true,
target: ts.ScriptTarget.ESNext,
};
export const defaultRuntimeCompileOptions: ts.CompilerOptions = {
outDir: undefined,
};
export const defaultTranspileOptions: ts.CompilerOptions = {
esModuleInterop: true,
module: ts.ModuleKind.ESNext,
sourceMap: true,
scriptComments: true,
target: ts.ScriptTarget.ESNext,
};
const ignoredCompilerOptions: readonly string[] = [
"allowSyntheticDefaultImports",
"baseUrl",
"build",
"composite",
"declaration",
"declarationDir",
"declarationMap",
"diagnostics",
"downlevelIteration",
"emitBOM",
"emitDeclarationOnly",
"esModuleInterop",
"extendedDiagnostics",
"forceConsistentCasingInFileNames",
"help",
"importHelpers",
"incremental",
"inlineSourceMap",
"inlineSources",
"init",
"isolatedModules",
"listEmittedFiles",
"listFiles",
"mapRoot",
"maxNodeModuleJsDepth",
"module",
"moduleResolution",
"newLine",
"noEmit",
"noEmitHelpers",
"noEmitOnError",
"noLib",
"noResolve",
"out",
"outDir",
"outFile",
"paths",
"preserveSymlinks",
"preserveWatchOutput",
"pretty",
"rootDir",
"rootDirs",
"showConfig",
"skipDefaultLibCheck",
"skipLibCheck",
"sourceMap",
"sourceRoot",
"stripInternal",
"target",
"traceResolution",
"tsBuildInfoFile",
"types",
"typeRoots",
"version",
"watch",
];
function getAssetInternal(filename: string): SourceFile {
const lastSegment = filename.split("/").pop()!;
const url = ts.libMap.has(lastSegment)
? ts.libMap.get(lastSegment)!
: lastSegment;
const sourceFile = SourceFile.get(url);
if (sourceFile) {
return sourceFile;
}
const name = url.includes(".") ? url : `${url}.d.ts`;
const sourceCode = getAsset(name);
return new SourceFile({
url,
filename: `${ASSETS}/${name}`,
mediaType: MediaType.TypeScript,
sourceCode,
});
}
export class Host implements ts.CompilerHost {
readonly #options = defaultCompileOptions;
#target: CompilerHostTarget;
#writeFile: WriteFileCallback;
/* Deno specific APIs */
constructor({
bundle = false,
target,
unstable,
writeFile,
}: CompilerHostOptions) {
this.#target = target;
this.#writeFile = writeFile;
if (bundle) {
// options we need to change when we are generating a bundle
Object.assign(this.#options, defaultBundlerOptions);
}
if (unstable) {
this.#options.lib = [
target === CompilerHostTarget.Worker
? "lib.deno.worker.d.ts"
: "lib.deno.window.d.ts",
"lib.deno.unstable.d.ts",
];
}
}
configure(
cwd: string,
path: string,
configurationText: string
): ConfigureResponse {
util.log("compiler::host.configure", path);
assert(configurationText);
const { config, error } = ts.parseConfigFileTextToJson(
path,
configurationText
);
if (error) {
return { diagnostics: [error] };
}
const { options, errors } = ts.convertCompilerOptionsFromJson(
config.compilerOptions,
cwd
);
const ignoredOptions: string[] = [];
for (const key of Object.keys(options)) {
if (
ignoredCompilerOptions.includes(key) &&
(!(key in this.#options) || options[key] !== this.#options[key])
) {
ignoredOptions.push(key);
delete options[key];
}
}
Object.assign(this.#options, options);
return {
ignoredOptions: ignoredOptions.length ? ignoredOptions : undefined,
diagnostics: errors.length ? errors : undefined,
};
}
mergeOptions(...options: ts.CompilerOptions[]): ts.CompilerOptions {
Object.assign(this.#options, ...options);
return Object.assign({}, this.#options);
}
/* TypeScript CompilerHost APIs */
fileExists(_fileName: string): boolean {
return notImplemented();
}
getCanonicalFileName(fileName: string): string {
return fileName;
}
getCompilationSettings(): ts.CompilerOptions {
util.log("compiler::host.getCompilationSettings()");
return this.#options;
}
getCurrentDirectory(): string {
return "";
}
getDefaultLibFileName(_options: ts.CompilerOptions): string {
util.log("compiler::host.getDefaultLibFileName()");
switch (this.#target) {
case CompilerHostTarget.Main:
case CompilerHostTarget.Runtime:
return `${ASSETS}/lib.deno.window.d.ts`;
case CompilerHostTarget.Worker:
return `${ASSETS}/lib.deno.worker.d.ts`;
}
}
getNewLine(): string {
return "\n";
}
getSourceFile(
fileName: string,
languageVersion: ts.ScriptTarget,
onError?: (message: string) => void,
shouldCreateNewSourceFile?: boolean
): ts.SourceFile | undefined {
util.log("compiler::host.getSourceFile", fileName);
try {
assert(!shouldCreateNewSourceFile);
const sourceFile = fileName.startsWith(ASSETS)
? getAssetInternal(fileName)
: SourceFile.get(fileName);
assert(sourceFile != null);
if (!sourceFile.tsSourceFile) {
assert(sourceFile.sourceCode != null);
const tsSourceFileName = fileName.startsWith(ASSETS)
? sourceFile.filename
: fileName;
sourceFile.tsSourceFile = ts.createSourceFile(
tsSourceFileName,
sourceFile.sourceCode,
languageVersion
);
delete sourceFile.sourceCode;
}
return sourceFile.tsSourceFile;
} catch (e) {
if (onError) {
onError(String(e));
} else {
throw e;
}
return undefined;
}
}
readFile(_fileName: string): string | undefined {
return notImplemented();
}
resolveModuleNames(
moduleNames: string[],
containingFile: string
): Array<ts.ResolvedModuleFull | undefined> {
util.log("compiler::host.resolveModuleNames", {
moduleNames,
containingFile,
});
return moduleNames.map((specifier) => {
const maybeUrl = SourceFile.getUrl(specifier, containingFile);
let sourceFile: SourceFile | undefined = undefined;
if (specifier.startsWith(ASSETS)) {
sourceFile = getAssetInternal(specifier);
} else if (typeof maybeUrl !== "undefined") {
sourceFile = SourceFile.get(maybeUrl);
}
if (!sourceFile) {
return undefined;
}
return {
resolvedFileName: sourceFile.url,
isExternalLibraryImport: specifier.startsWith(ASSETS),
extension: sourceFile.extension,
};
});
}
useCaseSensitiveFileNames(): boolean {
return true;
}
writeFile(
fileName: string,
data: string,
_writeByteOrderMark: boolean,
_onError?: (message: string) => void,
sourceFiles?: readonly ts.SourceFile[]
): void {
util.log("compiler::host.writeFile", fileName);
this.#writeFile(fileName, data, sourceFiles);
}
}