Skip to content
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

Interactive Diagnostics #31384

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
Expand Up @@ -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.
*/
Expand Down
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));
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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) {
Expand All @@ -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) };
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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 };
Expand Down
28 changes: 28 additions & 0 deletions src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
53 changes: 52 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -4547,6 +4594,7 @@ namespace ts {
*/
export interface DiagnosticMessageChain {
messageText: string;
annotations?: AnnotationSpan[];
category: DiagnosticCategory;
code: number;
next?: DiagnosticMessageChain;
Expand All @@ -4565,6 +4613,7 @@ namespace ts {
start: number | undefined;
length: number | undefined;
messageText: string | DiagnosticMessageChain;
annotations?: AnnotationSpan[];
}
export interface DiagnosticWithLocation extends Diagnostic {
file: SourceFile;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
78 changes: 72 additions & 6 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 } : {})
};
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
};
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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) {
Expand Down
Loading