Handle compiler diagnostics in Rust (#2445)

This commit is contained in:
Kitson Kelly 2019-06-04 23:03:56 +10:00 committed by Ryan Dahl
parent 60d4522641
commit a71305b4fe
16 changed files with 1044 additions and 87 deletions

View file

@ -1,6 +1,7 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
import * as msg from "gen/cli/msg_generated";
import { core } from "./core";
import { Diagnostic, fromTypeScriptDiagnostic } from "./diagnostics";
import * as flatbuffers from "./flatbuffers";
import { sendSync } from "./dispatch";
import { TextDecoder } from "./text_encoding";
@ -37,6 +38,11 @@ interface CompilerReq {
config?: string;
}
interface ConfigureResponse {
ignoredOptions?: string[];
diagnostics?: ts.Diagnostic[];
}
/** Options that either do nothing in Deno, or would cause undesired behavior
* if modified. */
const ignoredCompilerOptions: ReadonlyArray<string> = [
@ -105,6 +111,11 @@ interface ModuleMetaData {
sourceCode: string | undefined;
}
interface EmitResult {
emitSkipped: boolean;
diagnostics?: Diagnostic;
}
function fetchModuleMetaData(
specifier: string,
referrer: string
@ -193,22 +204,19 @@ class Host implements ts.CompilerHost {
* compiler's configuration options. The method returns an array of compiler
* options which were ignored, or `undefined`.
*/
configure(path: string, configurationText: string): string[] | undefined {
configure(path: string, configurationText: string): ConfigureResponse {
util.log("compile.configure", path);
const { config, error } = ts.parseConfigFileTextToJson(
path,
configurationText
);
if (error) {
this._logDiagnostics([error]);
return { diagnostics: [error] };
}
const { options, errors } = ts.convertCompilerOptionsFromJson(
config.compilerOptions,
cwd()
);
if (errors.length) {
this._logDiagnostics(errors);
}
const ignoredOptions: string[] = [];
for (const key of Object.keys(options)) {
if (
@ -220,7 +228,10 @@ class Host implements ts.CompilerHost {
}
}
Object.assign(this._options, options);
return ignoredOptions.length ? ignoredOptions : undefined;
return {
ignoredOptions: ignoredOptions.length ? ignoredOptions : undefined,
diagnostics: errors.length ? errors : undefined
};
}
getCompilationSettings(): ts.CompilerOptions {
@ -228,19 +239,6 @@ class Host implements ts.CompilerHost {
return this._options;
}
/** Log TypeScript diagnostics to the console and exit */
_logDiagnostics(diagnostics: ReadonlyArray<ts.Diagnostic>): never {
const errMsg = os.noColor
? ts.formatDiagnostics(diagnostics, this)
: ts.formatDiagnosticsWithColorAndContext(diagnostics, this);
console.log(errMsg);
// TODO The compiler isolate shouldn't call os.exit(). (In fact, it
// shouldn't even have access to call that op.) Errors should be forwarded
// to to the caller and the caller exit.
return os.exit(1);
}
fileExists(_fileName: string): boolean {
return notImplemented();
}
@ -362,10 +360,17 @@ class Host implements ts.CompilerHost {
window.compilerMain = function compilerMain(): void {
// workerMain should have already been called since a compiler is a worker.
window.onmessage = ({ data }: { data: CompilerReq }): void => {
let emitSkipped = true;
let diagnostics: ts.Diagnostic[] | undefined;
const { rootNames, configPath, config } = data;
const host = new Host();
if (config && config.length) {
const ignoredOptions = host.configure(configPath!, config);
// if there is a configuration supplied, we need to parse that
if (config && config.length && configPath) {
const configResult = host.configure(configPath, config);
const ignoredOptions = configResult.ignoredOptions;
diagnostics = configResult.diagnostics;
if (ignoredOptions) {
console.warn(
yellow(`Unsupported compiler options in "${configPath}"\n`) +
@ -377,51 +382,52 @@ window.compilerMain = function compilerMain(): void {
}
}
const options = host.getCompilationSettings();
const program = ts.createProgram(rootNames, options, host);
// if there was a configuration and no diagnostics with it, we will continue
// to generate the program and possibly emit it.
if (!diagnostics || (diagnostics && diagnostics.length === 0)) {
const options = host.getCompilationSettings();
const program = ts.createProgram(rootNames, options, host);
const preEmitDiagnostics = ts.getPreEmitDiagnostics(program).filter(
({ code }): boolean => {
// TS2691: An import path cannot end with a '.ts' extension. Consider
// importing 'bad-module' instead.
if (code === 2691) return false;
// TS5009: Cannot find the common subdirectory path for the input files.
if (code === 5009) return false;
// TS5055: Cannot write file
// 'http://localhost:4545/tests/subdir/mt_application_x_javascript.j4.js'
// because it would overwrite input file.
if (code === 5055) return false;
// TypeScript is overly opinionated that only CommonJS modules kinds can
// support JSON imports. Allegedly this was fixed in
// Microsoft/TypeScript#26825 but that doesn't seem to be working here,
// so we will ignore complaints about this compiler setting.
if (code === 5070) return false;
return true;
diagnostics = ts.getPreEmitDiagnostics(program).filter(
({ code }): boolean => {
// TS2691: An import path cannot end with a '.ts' extension. Consider
// importing 'bad-module' instead.
if (code === 2691) return false;
// TS5009: Cannot find the common subdirectory path for the input files.
if (code === 5009) return false;
// TS5055: Cannot write file
// 'http://localhost:4545/tests/subdir/mt_application_x_javascript.j4.js'
// because it would overwrite input file.
if (code === 5055) return false;
// TypeScript is overly opinionated that only CommonJS modules kinds can
// support JSON imports. Allegedly this was fixed in
// Microsoft/TypeScript#26825 but that doesn't seem to be working here,
// so we will ignore complaints about this compiler setting.
if (code === 5070) return false;
return true;
}
);
// We will only proceed with the emit if there are no diagnostics.
if (diagnostics && diagnostics.length === 0) {
const emitResult = program.emit();
emitSkipped = emitResult.emitSkipped;
// emitResult.diagnostics is `readonly` in TS3.5+ and can't be assigned
// without casting.
diagnostics = emitResult.diagnostics as ts.Diagnostic[];
}
);
if (preEmitDiagnostics.length > 0) {
host._logDiagnostics(preEmitDiagnostics);
// The above _logDiagnostics calls os.exit(). The return is here just for
// clarity.
return;
}
const emitResult = program!.emit();
const result: EmitResult = {
emitSkipped,
diagnostics: diagnostics.length
? fromTypeScriptDiagnostic(diagnostics)
: undefined
};
// TODO(ry) Print diagnostics in Rust.
// https://github.com/denoland/deno/pull/2310
postMessage(result);
const { diagnostics } = emitResult;
if (diagnostics.length > 0) {
host._logDiagnostics(diagnostics);
// The above _logDiagnostics calls os.exit(). The return is here just for
// clarity.
return;
}
postMessage(emitResult);
// The compiler isolate exits after a single messsage.
// The compiler isolate exits after a single message.
workerClose();
};
};