Skip to content

Interactive Diagnostics #31384

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
  •  
  •  
  •  
200 changes: 170 additions & 30 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions src/compiler/factory.ts
Original file line number Diff line number Diff line change
@@ -3207,6 +3207,21 @@ namespace ts {
return node;
}

/**
* Sets a callback to use to expand an ellision node (eg, ...) with a more complete node tree
*/
/*@internal*/
export function setExpansionCallback<T extends Node>(node: T, expansionCb: EmitNode["expansion"]) {
getOrCreateEmitNode(node).expansion = expansionCb;
return node;
}

/*@internal*/
export function getExpansionCallback(node: Node) {
const emitNode = node.emitNode;
return emitNode && emitNode.expansion;
}

/**
* Gets a custom text range to use when emitting comments.
*/
14 changes: 7 additions & 7 deletions src/compiler/moduleNameResolver.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace ts {
/* @internal */
export function trace(host: ModuleResolutionHost, message: DiagnosticMessage, ...args: any[]): void;
export function trace(host: ModuleResolutionHost, message: DiagnosticMessage, ...args: (string | number)[]): void;
export function trace(host: ModuleResolutionHost): void {
host.trace!(formatMessage.apply(undefined, arguments));
}
@@ -133,7 +133,7 @@ namespace ts {
if (fileName === undefined) return;
const path = normalizePath(combinePaths(baseDirectory, fileName));
if (state.traceEnabled) {
trace(state.host, Diagnostics.package_json_has_0_field_1_that_references_2, fieldName, fileName, path);
trace(state.host, Diagnostics.package_json_has_0_field_1_that_references_2, fieldName, fileName!, path);
}
return path;
}
@@ -279,15 +279,15 @@ namespace ts {
trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_not_set_root_directory_not_set, typeReferenceDirectiveName);
}
else {
trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_not_set_root_directory_1, typeReferenceDirectiveName, typeRoots);
trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_not_set_root_directory_1, typeReferenceDirectiveName, String(typeRoots));
}
}
else {
if (typeRoots === undefined) {
trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_1_root_directory_not_set, typeReferenceDirectiveName, containingFile);
}
else {
trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_1_root_directory_2, typeReferenceDirectiveName, containingFile, typeRoots);
trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_1_root_directory_2, typeReferenceDirectiveName, containingFile, String(typeRoots));
}
}
if (redirectedReference) {
@@ -307,7 +307,7 @@ namespace ts {
const { fileName, packageId } = resolved;
const resolvedFileName = options.preserveSymlinks ? fileName : realPath(fileName, host, traceEnabled);
if (traceEnabled) {
trace(host, Diagnostics.Type_reference_directive_0_was_successfully_resolved_to_1_primary_Colon_2, typeReferenceDirectiveName, resolvedFileName, primary);
trace(host, Diagnostics.Type_reference_directive_0_was_successfully_resolved_to_1_primary_Colon_2, typeReferenceDirectiveName, resolvedFileName, String(primary));
}
resolvedTypeReferenceDirective = { primary, resolvedFileName, packageId, isExternalLibraryImport: pathContainsNodeModules(fileName) };
}
@@ -796,7 +796,7 @@ namespace ts {
(matchedNormalizedPrefix === undefined || matchedNormalizedPrefix.length < normalizedRoot.length);

if (state.traceEnabled) {
trace(state.host, Diagnostics.Checking_if_0_is_the_longest_matching_prefix_for_1_2, normalizedRoot, candidate, isLongestMatchingPrefix);
trace(state.host, Diagnostics.Checking_if_0_is_the_longest_matching_prefix_for_1_2, normalizedRoot, candidate, String(isLongestMatchingPrefix));
}

if (isLongestMatchingPrefix) {
@@ -1506,7 +1506,7 @@ namespace ts {
export function loadModuleFromGlobalCache(moduleName: string, projectName: string | undefined, compilerOptions: CompilerOptions, host: ModuleResolutionHost, globalCache: string): ResolvedModuleWithFailedLookupLocations {
const traceEnabled = isTraceEnabled(compilerOptions, host);
if (traceEnabled) {
trace(host, Diagnostics.Auto_discovery_for_typings_is_enabled_in_project_0_Running_extra_resolution_pass_for_module_1_using_cache_location_2, projectName, moduleName, globalCache);
trace(host, Diagnostics.Auto_discovery_for_typings_is_enabled_in_project_0_Running_extra_resolution_pass_for_module_1_using_cache_location_2, projectName === undefined ? "unnamed project" : projectName, moduleName, globalCache);
}
const failedLookupLocations: string[] = [];
const state: ModuleResolutionState = { compilerOptions, host, traceEnabled, failedLookupLocations };
28 changes: 28 additions & 0 deletions src/compiler/program.ts
Original file line number Diff line number Diff line change
@@ -502,6 +502,34 @@ namespace ts {
return output;
}

/* @internal */
export function flattenDiagnosticAnnotationSpans(spans: AnnotationSpan[] | DiagnosticMessageChain | undefined, newLine: string): AnnotationSpan[] | undefined {
if (!spans || isArray(spans)) {
return spans;
}
let results: AnnotationSpan[] | undefined;
let diagnosticChain: DiagnosticMessageChain | undefined = spans;
let offset = 0;

let indent = 0;
while (diagnosticChain) {
if (indent) {
offset += newLine.length;

for (let i = 0; i < indent; i++) {
offset += " ".length;
}
}
if (diagnosticChain.annotations) {
results = concatenate(results, map(diagnosticChain.annotations, d => ({ ...d, start: d.start + offset })));
}
offset += diagnosticChain.messageText.length;
indent++;
diagnosticChain = diagnosticChain.next;
}
return results;
}

export function flattenDiagnosticMessageText(messageText: string | DiagnosticMessageChain | undefined, newLine: string): string {
if (isString(messageText)) {
return messageText;
53 changes: 52 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
@@ -3319,6 +3319,51 @@ namespace ts {
runWithCancellationToken<T>(token: CancellationToken, cb: (checker: TypeChecker) => T): T;

/* @internal */ getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol: Symbol): ReadonlyArray<TypeParameter> | undefined;

/* @internal */ getDiagnosticRenderingContext(flags: DiagnosticRendererFlags): DiagnosticRenderContext;

id: number;

getExpandedReveal(id: number): RevealedResult | undefined;
}


/** @internal */
export enum DiagnosticRendererFlags {
None = 0,
UseFullyQualifiedTypes = 1 << 0,
}

export type AnnotationSpan = SymbolSpan | RevealSpan;

export interface AnnotationSpanBase {
kind: AnnotationSpan["kind"];
start: number;
length: number;
}

export interface SymbolSpan extends AnnotationSpanBase {
kind: "symbol";
symbol: Symbol;
}

export interface RevealedResult {
text: string;
annotations?: AnnotationSpan[];
}

export interface RevealSpan extends AnnotationSpanBase {
kind: "reveal";
id: number;
checker: number;
callback: () => RevealedResult;
}

/** @internal */
export interface DiagnosticRenderContext {
typeToString(type: Type, symbolOffset: number): string;
symbolToString(symbol: Symbol, symbolOffset: number): string;
getPendingAnnotationSpans(): AnnotationSpan[] | undefined;
}

/* @internal */
@@ -4009,6 +4054,8 @@ namespace ts {
restrictiveInstantiation?: Type; // Instantiation with type parameters mapped to unconstrained form
/* @internal */
immediateBaseConstraint?: Type; // Immediate base constraint cache
/* @internal */
escapedName?: undefined; // Never set
}

/* @internal */
@@ -4043,8 +4090,8 @@ namespace ts {

// Unique symbol types (TypeFlags.UniqueESSymbol)
export interface UniqueESSymbolType extends Type {
__uniqueESSymbolBrand: any;
symbol: Symbol;
escapedName: __String;
}

export interface StringLiteralType extends LiteralType {
@@ -4547,6 +4594,7 @@ namespace ts {
*/
export interface DiagnosticMessageChain {
messageText: string;
annotations?: AnnotationSpan[];
category: DiagnosticCategory;
code: number;
next?: DiagnosticMessageChain;
@@ -4565,6 +4613,7 @@ namespace ts {
start: number | undefined;
length: number | undefined;
messageText: string | DiagnosticMessageChain;
annotations?: AnnotationSpan[];
}
export interface DiagnosticWithLocation extends Diagnostic {
file: SourceFile;
@@ -5260,6 +5309,7 @@ namespace ts {
externalHelpersModuleName?: Identifier; // The local name for an imported helpers module
helpers?: EmitHelper[]; // Emit helpers for the node
startsOnNewLine?: boolean; // If the node should begin on a new line
expansion?: () => Node; // A callback that returns a node tree representing an expansion of this node tree
}

export const enum EmitFlags {
@@ -5804,6 +5854,7 @@ namespace ts {
getIndent(): number;
isAtStartOfLine(): boolean;
getTextPosWithWriteLine?(): number;
writeExpansionSpan?(start: number, length: number, printCallback: (writer: EmitTextWriter) => string): void;
}

export interface GetEffectiveTypeRootsHost {
78 changes: 72 additions & 6 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
@@ -865,7 +865,8 @@ namespace ts {
code: messageChain.code,
category: messageChain.category,
messageText: messageChain.next ? messageChain : messageChain.messageText,
relatedInformation
relatedInformation,
...(!messageChain.next && messageChain.annotations ? { annotations: messageChain.annotations } : {})
};
}

@@ -3194,7 +3195,7 @@ namespace ts {
return indentStrings[1].length;
}

export function createTextWriter(newLine: string): EmitTextWriter {
export function createTextWriter(newLine: string, writeSymbol?: ((t: string, s: Symbol) => string), writeExpansionSpan?: EmitTextWriter["writeExpansionSpan"]): EmitTextWriter {
let output: string;
let indent: number;
let lineStart: boolean;
@@ -3285,10 +3286,11 @@ namespace ts {
writePunctuation: write,
writeSpace: write,
writeStringLiteral: write,
writeSymbol: (s, _) => write(s),
writeSymbol: (s, _) => write(writeSymbol ? writeSymbol(s, _) : s),
writeTrailingSemicolon: write,
writeComment: write,
getTextPosWithWriteLine
getTextPosWithWriteLine,
writeExpansionSpan
};
}

@@ -7107,8 +7109,27 @@ namespace ts {
getSourceMapSourceConstructor: () => <any>SourceMapSource,
};

export function formatStringFromArgs(text: string, args: ArrayLike<string | number>, baseIndex = 0): string {
return text.replace(/{(\d+)}/g, (_match, index: string) => "" + Debug.assertDefined(args[+index + baseIndex]));
function renderForOutput(arg: string | number | Type | Symbol, renderContext: DiagnosticRenderContext | undefined, offset: number) {
Debug.assertDefined(arg);
if (typeof arg === "string" || typeof arg === "number") {
return arg;
}
if (!renderContext) {
return Debug.fail("Type or symbol passed into diagnostic rendering pipeline with no renderer provided.");
}
if (arg.escapedName) {
return renderContext.symbolToString(arg, offset);
}
return renderContext.typeToString(arg as Type, offset);
}

export function formatStringFromArgs(text: string, args: ArrayLike<string | number | Type | Symbol>, baseIndex = 0, renderContext?: DiagnosticRenderContext): string {
let offsetAdjustmentFromReplacement = 0;
return text.replace(/{(\d+)}/g, (match: string, index: string, offset: number) => {
const text = "" + renderForOutput(args[+index + baseIndex], renderContext, offset + offsetAdjustmentFromReplacement);
offsetAdjustmentFromReplacement += text.length - match.length;
return text;
});
}

export let localizedDiagnosticMessages: MapLike<string> | undefined;
@@ -7176,6 +7197,30 @@ namespace ts {
};
}

export function createRenderedCompilerDiagnostic(checker: TypeChecker, flags: DiagnosticRendererFlags, message: DiagnosticMessage, ...args: (string | number | Type | Symbol | undefined)[]): Diagnostic;
export function createRenderedCompilerDiagnostic(checker: TypeChecker, flags: DiagnosticRendererFlags, message: DiagnosticMessage): Diagnostic {
let text = getLocaleSpecificMessage(message);
let spans: AnnotationSpan[] | undefined;

if (arguments.length > 3) {
const ctx = checker.getDiagnosticRenderingContext(flags);
text = formatStringFromArgs(text, arguments, 3, ctx);
spans = ctx.getPendingAnnotationSpans();
}

return {
file: undefined,
start: undefined,
length: undefined,

messageText: text,
category: message.category,
code: message.code,
reportsUnnecessary: message.reportsUnnecessary,
...(typeof spans === "undefined" ? {} : { annotations: spans })
};
}

export function createCompilerDiagnosticFromMessageChain(chain: DiagnosticMessageChain): Diagnostic {
return {
file: undefined,
@@ -7205,6 +7250,27 @@ namespace ts {
};
}

export function chainRenderedDiagnosticMessages(checker: TypeChecker, flags: DiagnosticRendererFlags, details: DiagnosticMessageChain | undefined, message: DiagnosticMessage, ...args: (string | number | Type | Symbol | undefined)[]): DiagnosticMessageChain;
export function chainRenderedDiagnosticMessages(checker: TypeChecker, flags: DiagnosticRendererFlags, details: DiagnosticMessageChain | undefined, message: DiagnosticMessage): DiagnosticMessageChain {
let text = getLocaleSpecificMessage(message);
let spans: AnnotationSpan[] | undefined;

if (arguments.length > 4) {
const ctx = checker.getDiagnosticRenderingContext(flags);
text = formatStringFromArgs(text, arguments, 4, ctx);
spans = ctx.getPendingAnnotationSpans();
}

return {
messageText: text,
category: message.category,
code: message.code,

next: details,
...(typeof spans === "undefined" ? {} : { annotations: spans })
};
}

export function concatenateDiagnosticMessageChains(headChain: DiagnosticMessageChain, tailChain: DiagnosticMessageChain): DiagnosticMessageChain {
let lastChain = headChain;
while (lastChain.next) {
Loading