summaryrefslogtreecommitdiff
path: root/cli/js/compiler.ts
diff options
context:
space:
mode:
Diffstat (limited to 'cli/js/compiler.ts')
-rw-r--r--cli/js/compiler.ts148
1 files changed, 119 insertions, 29 deletions
diff --git a/cli/js/compiler.ts b/cli/js/compiler.ts
index d86109278..af95390e7 100644
--- a/cli/js/compiler.ts
+++ b/cli/js/compiler.ts
@@ -32,8 +32,16 @@ function getAsset(name: string): string {
// Constants used by `normalizeString` and `resolvePath`
const CHAR_DOT = 46; /* . */
const CHAR_FORWARD_SLASH = 47; /* / */
-const ASSETS = "$asset$";
-const OUT_DIR = "$deno$";
+// Using incremental compile APIs requires that all
+// paths must be either relative or absolute. Since
+// analysis in Rust operates on fully resolved URLs,
+// it makes sense to use the same scheme here.
+const ASSETS = "asset://";
+const OUT_DIR = "deno://";
+// This constant is passed to compiler settings when
+// doing incremental compiles. Contents of this
+// file are passed back to Rust and saved to $DENO_DIR.
+const TS_BUILD_INFO = "cache:///tsbuildinfo.json";
// TODO(Bartlomieju): this check should be done in Rust
const IGNORED_COMPILER_OPTIONS: readonly string[] = [
@@ -104,6 +112,24 @@ const DEFAULT_BUNDLER_OPTIONS: ts.CompilerOptions = {
sourceMap: false,
};
+const DEFAULT_INCREMENTAL_COMPILE_OPTIONS: ts.CompilerOptions = {
+ allowJs: false,
+ allowNonTsExtensions: true,
+ checkJs: false,
+ esModuleInterop: true,
+ incremental: true,
+ inlineSourceMap: true,
+ jsx: ts.JsxEmit.React,
+ module: ts.ModuleKind.ESNext,
+ outDir: OUT_DIR,
+ resolveJsonModule: true,
+ sourceMap: false,
+ strict: true,
+ stripComments: true,
+ target: ts.ScriptTarget.ESNext,
+ tsBuildInfoFile: TS_BUILD_INFO,
+};
+
const DEFAULT_COMPILE_OPTIONS: ts.CompilerOptions = {
allowJs: false,
allowNonTsExtensions: true,
@@ -142,6 +168,12 @@ interface CompilerHostOptions {
target: CompilerHostTarget;
unstable?: boolean;
writeFile: WriteFileCallback;
+ incremental?: boolean;
+}
+
+interface IncrementalCompilerHostOptions extends CompilerHostOptions {
+ rootNames?: string[];
+ buildInfo?: string;
}
interface ConfigureResponse {
@@ -166,6 +198,7 @@ interface SourceFileJson {
filename: string;
mediaType: MediaType;
sourceCode: string;
+ versionHash: string;
}
function getExtension(fileName: string, mediaType: MediaType): ts.Extension {
@@ -274,19 +307,20 @@ function getAssetInternal(filename: string): SourceFile {
url,
filename: `${ASSETS}/${name}`,
mediaType: MediaType.TypeScript,
+ versionHash: "1",
sourceCode,
});
}
class Host implements ts.CompilerHost {
- readonly #options = DEFAULT_COMPILE_OPTIONS;
+ protected _options = DEFAULT_COMPILE_OPTIONS;
#target: CompilerHostTarget;
#writeFile: WriteFileCallback;
-
/* Deno specific APIs */
constructor({
bundle = false,
+ incremental = false,
target,
unstable,
writeFile,
@@ -295,10 +329,12 @@ class Host implements ts.CompilerHost {
this.#writeFile = writeFile;
if (bundle) {
// options we need to change when we are generating a bundle
- Object.assign(this.#options, DEFAULT_BUNDLER_OPTIONS);
+ Object.assign(this._options, DEFAULT_BUNDLER_OPTIONS);
+ } else if (incremental) {
+ Object.assign(this._options, DEFAULT_INCREMENTAL_COMPILE_OPTIONS);
}
if (unstable) {
- this.#options.lib = [
+ this._options.lib = [
target === CompilerHostTarget.Worker
? "lib.deno.worker.d.ts"
: "lib.deno.window.d.ts",
@@ -308,7 +344,7 @@ class Host implements ts.CompilerHost {
}
get options(): ts.CompilerOptions {
- return this.#options;
+ return this._options;
}
configure(
@@ -333,13 +369,13 @@ class Host implements ts.CompilerHost {
for (const key of Object.keys(options)) {
if (
IGNORED_COMPILER_OPTIONS.includes(key) &&
- (!(key in this.#options) || options[key] !== this.#options[key])
+ (!(key in this._options) || options[key] !== this._options[key])
) {
ignoredOptions.push(key);
delete options[key];
}
}
- Object.assign(this.#options, options);
+ Object.assign(this._options, options);
return {
ignoredOptions: ignoredOptions.length ? ignoredOptions : undefined,
diagnostics: errors.length ? errors : undefined,
@@ -347,8 +383,8 @@ class Host implements ts.CompilerHost {
}
mergeOptions(...options: ts.CompilerOptions[]): ts.CompilerOptions {
- Object.assign(this.#options, ...options);
- return Object.assign({}, this.#options);
+ Object.assign(this._options, ...options);
+ return Object.assign({}, this._options);
}
/* TypeScript CompilerHost APIs */
@@ -363,7 +399,7 @@ class Host implements ts.CompilerHost {
getCompilationSettings(): ts.CompilerOptions {
log("compiler::host.getCompilationSettings()");
- return this.#options;
+ return this._options;
}
getCurrentDirectory(): string {
@@ -409,6 +445,8 @@ class Host implements ts.CompilerHost {
sourceFile.sourceCode,
languageVersion
);
+ //@ts-ignore
+ sourceFile.tsSourceFile.version = sourceFile.versionHash;
delete sourceFile.sourceCode;
}
return sourceFile.tsSourceFile;
@@ -480,6 +518,25 @@ class Host implements ts.CompilerHost {
}
}
+class IncrementalCompileHost extends Host {
+ #buildInfo: undefined | string = undefined;
+
+ constructor(options: IncrementalCompilerHostOptions) {
+ super(options);
+ const { buildInfo } = options;
+ if (buildInfo) {
+ this.#buildInfo = buildInfo;
+ }
+ }
+
+ readFile(fileName: string): string | undefined {
+ if (fileName == TS_BUILD_INFO) {
+ return this.#buildInfo;
+ }
+ throw new Error("unreachable");
+ }
+}
+
// NOTE: target doesn't really matter here,
// this is in fact a mock host created just to
// load all type definitions and snapshot them.
@@ -547,6 +604,7 @@ function buildLocalSourceFileCache(
filename: entry.url,
mediaType: entry.mediaType,
sourceCode: entry.sourceCode,
+ versionHash: entry.versionHash,
});
for (const importDesc of entry.imports) {
@@ -598,6 +656,7 @@ function buildSourceFileCache(
filename: entry.url,
mediaType: entry.mediaType,
sourceCode: entry.sourceCode,
+ versionHash: entry.versionHash,
});
for (const importDesc of entry.imports) {
@@ -663,6 +722,7 @@ type WriteFileCallback = (
interface CompileWriteFileState {
rootNames: string[];
emitMap: Record<string, EmittedSource>;
+ buildInfo?: string;
}
interface BundleWriteFileState {
@@ -708,7 +768,15 @@ function createCompileWriteFile(
data: string,
sourceFiles?: readonly ts.SourceFile[]
): void {
- assert(sourceFiles != null);
+ const isBuildInfo = fileName === TS_BUILD_INFO;
+
+ if (isBuildInfo) {
+ assert(isBuildInfo);
+ state.buildInfo = data;
+ return;
+ }
+
+ assert(sourceFiles);
assert(sourceFiles.length === 1);
state.emitMap[fileName] = {
filename: sourceFiles[0].fileName,
@@ -717,6 +785,22 @@ function createCompileWriteFile(
};
}
+function createRuntimeCompileWriteFile(
+ state: CompileWriteFileState
+): WriteFileCallback {
+ return function writeFile(
+ fileName: string,
+ data: string,
+ sourceFiles?: readonly ts.SourceFile[]
+ ): void {
+ assert(sourceFiles);
+ assert(sourceFiles.length === 1);
+ state.emitMap[fileName] = {
+ filename: sourceFiles[0].fileName,
+ contents: data,
+ };
+ };
+}
interface ConvertCompilerOptionsResult {
files?: string[];
options: ts.CompilerOptions;
@@ -888,7 +972,6 @@ function performanceEnd(): Stats {
}
// TODO(Bartlomieju): this check should be done in Rust; there should be no
-// console.log here
function processConfigureResponse(
configResult: ConfigureResponse,
configPath: string
@@ -1106,6 +1189,7 @@ interface SourceFileMapEntry {
libDirectives: ReferenceDescriptor[];
typesDirectives: ReferenceDescriptor[];
typeHeaders: ReferenceDescriptor[];
+ versionHash: string;
}
/** Used when "deno run" is invoked */
@@ -1121,6 +1205,7 @@ interface CompileRequest {
cwd: string;
// key value is fully resolved URL
sourceFileMap: Record<string, SourceFileMapEntry>;
+ buildInfo?: string;
}
/** Used when "deno bundle" is invoked */
@@ -1174,6 +1259,7 @@ type CompilerRequest =
interface CompileResponse {
emitMap: Record<string, EmittedSource>;
diagnostics: Diagnostic;
+ buildInfo: undefined | string;
stats?: Stats;
}
@@ -1195,19 +1281,16 @@ interface RuntimeBundleResponse {
function compile({
allowJs,
+ buildInfo,
config,
configPath,
rootNames,
target,
unstable,
- performance,
cwd,
sourceFileMap,
type,
}: CompileRequest): CompileResponse {
- if (performance) {
- performanceStart();
- }
log(">>> compile start", { rootNames, type: CompilerRequestType[type] });
// When a programme is emitted, TypeScript will call `writeFile` with
@@ -1218,11 +1301,14 @@ function compile({
rootNames,
emitMap: {},
};
- const host = new Host({
+ const host = new IncrementalCompileHost({
bundle: false,
target,
unstable,
+ incremental: true,
writeFile: createCompileWriteFile(state),
+ rootNames,
+ buildInfo,
});
let diagnostics: readonly ts.Diagnostic[] = [];
@@ -1239,15 +1325,23 @@ function compile({
// to generate the program and possibly emit it.
if (diagnostics.length === 0) {
const options = host.getCompilationSettings();
- const program = ts.createProgram({
+ const program = ts.createIncrementalProgram({
rootNames,
options,
host,
});
- diagnostics = ts
- .getPreEmitDiagnostics(program)
- .filter(({ code }) => !ignoredDiagnostics.includes(code));
+ // TODO(bartlomieju): check if this is ok
+ diagnostics = [
+ ...program.getConfigFileParsingDiagnostics(),
+ ...program.getSyntacticDiagnostics(),
+ ...program.getOptionsDiagnostics(),
+ ...program.getGlobalDiagnostics(),
+ ...program.getSemanticDiagnostics(),
+ ];
+ diagnostics = diagnostics.filter(
+ ({ code }) => !ignoredDiagnostics.includes(code)
+ );
// We will only proceed with the emit if there are no diagnostics.
if (diagnostics.length === 0) {
@@ -1265,18 +1359,14 @@ function compile({
// without casting.
diagnostics = emitResult.diagnostics;
}
- if (performance) {
- performanceProgram(program);
- }
}
log("<<< compile end", { rootNames, type: CompilerRequestType[type] });
- const stats = performance ? performanceEnd() : undefined;
return {
emitMap: state.emitMap,
+ buildInfo: state.buildInfo,
diagnostics: fromTypeScriptDiagnostic(diagnostics),
- stats,
};
}
@@ -1402,7 +1492,7 @@ function runtimeCompile(
const host = new Host({
bundle: false,
target,
- writeFile: createCompileWriteFile(state),
+ writeFile: createRuntimeCompileWriteFile(state),
});
const compilerOptions = [DEFAULT_RUNTIME_COMPILE_OPTIONS];
if (convertedOptions) {