Skip to content

Commit

Permalink
Refactor tsserver, tsc and fourslash-server tests so that paths are a…
Browse files Browse the repository at this point in the history
…lways watchable (#59844)
  • Loading branch information
sheetalkamat authored Sep 4, 2024
1 parent 29d92ed commit 7976d9c
Show file tree
Hide file tree
Showing 2,663 changed files with 281,347 additions and 251,727 deletions.
28 changes: 17 additions & 11 deletions src/compiler/utilitiesPublic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,28 +303,34 @@ export function sortAndDeduplicateDiagnostics<T extends Diagnostic>(diagnostics:
return sortAndDeduplicate<T>(diagnostics, compareDiagnostics, diagnosticsEqualityComparer);
}

/** @internal */
export const targetToLibMap = new Map<ScriptTarget, string>([
[ScriptTarget.ESNext, "lib.esnext.full.d.ts"],
[ScriptTarget.ES2023, "lib.es2023.full.d.ts"],
[ScriptTarget.ES2022, "lib.es2022.full.d.ts"],
[ScriptTarget.ES2021, "lib.es2021.full.d.ts"],
[ScriptTarget.ES2020, "lib.es2020.full.d.ts"],
[ScriptTarget.ES2019, "lib.es2019.full.d.ts"],
[ScriptTarget.ES2018, "lib.es2018.full.d.ts"],
[ScriptTarget.ES2017, "lib.es2017.full.d.ts"],
[ScriptTarget.ES2016, "lib.es2016.full.d.ts"],
[ScriptTarget.ES2015, "lib.es6.d.ts"], // We don't use lib.es2015.full.d.ts due to breaking change.
]);

export function getDefaultLibFileName(options: CompilerOptions): string {
switch (getEmitScriptTarget(options)) {
const target = getEmitScriptTarget(options);
switch (target) {
case ScriptTarget.ESNext:
return "lib.esnext.full.d.ts";
case ScriptTarget.ES2023:
return "lib.es2023.full.d.ts";
case ScriptTarget.ES2022:
return "lib.es2022.full.d.ts";
case ScriptTarget.ES2021:
return "lib.es2021.full.d.ts";
case ScriptTarget.ES2020:
return "lib.es2020.full.d.ts";
case ScriptTarget.ES2019:
return "lib.es2019.full.d.ts";
case ScriptTarget.ES2018:
return "lib.es2018.full.d.ts";
case ScriptTarget.ES2017:
return "lib.es2017.full.d.ts";
case ScriptTarget.ES2016:
return "lib.es2016.full.d.ts";
case ScriptTarget.ES2015:
return "lib.es6.d.ts"; // We don't use lib.es2015.full.d.ts due to breaking change.
return targetToLibMap.get(target)!;
default:
return "lib.d.ts";
}
Expand Down
215 changes: 0 additions & 215 deletions src/harness/fakesHosts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -422,218 +422,3 @@ export class CompilerHost implements ts.CompilerHost {
return parsed;
}
}

export type ExpectedDiagnosticMessage = [ts.DiagnosticMessage, ...(string | number)[]];
export interface ExpectedDiagnosticMessageChain {
message: ExpectedDiagnosticMessage;
next?: ExpectedDiagnosticMessageChain[];
}

export interface ExpectedDiagnosticLocation {
file: string;
start: number;
length: number;
}
export interface ExpectedDiagnosticRelatedInformation extends ExpectedDiagnosticMessageChain {
location?: ExpectedDiagnosticLocation;
}

export enum DiagnosticKind {
Error = "Error",
Status = "Status",
}
export interface ExpectedErrorDiagnostic extends ExpectedDiagnosticRelatedInformation {
relatedInformation?: ExpectedDiagnosticRelatedInformation[];
}

export type ExpectedDiagnostic = ExpectedDiagnosticMessage | ExpectedErrorDiagnostic;

interface SolutionBuilderDiagnostic {
kind: DiagnosticKind;
diagnostic: ts.Diagnostic;
}

function indentedText(indent: number, text: string) {
if (!indent) return text;
let indentText = "";
for (let i = 0; i < indent; i++) {
indentText += " ";
}
return `
${indentText}${text}`;
}

function expectedDiagnosticMessageToText([message, ...args]: ExpectedDiagnosticMessage) {
let text = ts.getLocaleSpecificMessage(message);
if (args.length) {
text = ts.formatStringFromArgs(text, args);
}
return text;
}

function expectedDiagnosticMessageChainToText({ message, next }: ExpectedDiagnosticMessageChain, indent = 0) {
let text = indentedText(indent, expectedDiagnosticMessageToText(message));
if (next) {
indent++;
next.forEach(kid => text += expectedDiagnosticMessageChainToText(kid, indent));
}
return text;
}

function expectedDiagnosticRelatedInformationToText({ location, ...diagnosticMessage }: ExpectedDiagnosticRelatedInformation) {
const text = expectedDiagnosticMessageChainToText(diagnosticMessage);
if (location) {
const { file, start, length } = location;
return `${file}(${start}:${length}):: ${text}`;
}
return text;
}

function expectedErrorDiagnosticToText({ relatedInformation, ...diagnosticRelatedInformation }: ExpectedErrorDiagnostic) {
let text = `${DiagnosticKind.Error}!: ${expectedDiagnosticRelatedInformationToText(diagnosticRelatedInformation)}`;
if (relatedInformation) {
for (const kid of relatedInformation) {
text += `
related:: ${expectedDiagnosticRelatedInformationToText(kid)}`;
}
}
return text;
}

function expectedDiagnosticToText(errorOrStatus: ExpectedDiagnostic) {
return ts.isArray(errorOrStatus) ?
`${DiagnosticKind.Status}!: ${expectedDiagnosticMessageToText(errorOrStatus)}` :
expectedErrorDiagnosticToText(errorOrStatus);
}

function diagnosticMessageChainToText({ messageText, next }: ts.DiagnosticMessageChain, indent = 0) {
let text = indentedText(indent, messageText);
if (next) {
indent++;
next.forEach(kid => text += diagnosticMessageChainToText(kid, indent));
}
return text;
}

function diagnosticRelatedInformationToText({ file, start, length, messageText }: ts.DiagnosticRelatedInformation) {
const text = typeof messageText === "string" ?
messageText :
diagnosticMessageChainToText(messageText);
return file ?
`${file.fileName}(${start}:${length}):: ${text}` :
text;
}

function diagnosticToText({ kind, diagnostic: { relatedInformation, ...diagnosticRelatedInformation } }: SolutionBuilderDiagnostic) {
let text = `${kind}!: ${diagnosticRelatedInformationToText(diagnosticRelatedInformation)}`;
if (relatedInformation) {
for (const kid of relatedInformation) {
text += `
related:: ${diagnosticRelatedInformationToText(kid)}`;
}
}
return text;
}

export const version = "FakeTSVersion";

export function patchHostForBuildInfoReadWrite<T extends ts.System>(sys: T) {
const originalReadFile = sys.readFile;
sys.readFile = (path, encoding) => {
const value = originalReadFile.call(sys, path, encoding);
if (!value || !ts.isBuildInfoFile(path)) return value;
const buildInfo = ts.getBuildInfo(path, value);
if (!buildInfo) return value;
ts.Debug.assert(buildInfo.version === version);
buildInfo.version = ts.version;
return ts.getBuildInfoText(buildInfo);
};
return patchHostForBuildInfoWrite(sys, version);
}

export function patchHostForBuildInfoWrite<T extends ts.System>(sys: T, version: string) {
const originalWrite = sys.write;
sys.write = msg => originalWrite.call(sys, msg.replace(ts.version, version));
const originalWriteFile = sys.writeFile;
sys.writeFile = (fileName: string, content: string, writeByteOrderMark: boolean) => {
if (ts.isBuildInfoFile(fileName)) {
const buildInfo = ts.getBuildInfo(fileName, content);
if (buildInfo) {
buildInfo.version = version;
return originalWriteFile.call(sys, fileName, ts.getBuildInfoText(buildInfo), writeByteOrderMark);
}
}
return originalWriteFile.call(sys, fileName, content, writeByteOrderMark);
};
return sys;
}

export class SolutionBuilderHost extends CompilerHost implements ts.SolutionBuilderHost<ts.BuilderProgram> {
createProgram: ts.CreateProgram<ts.BuilderProgram>;

private constructor(sys: System | vfs.FileSystem, options?: ts.CompilerOptions, setParentNodes?: boolean, createProgram?: ts.CreateProgram<ts.BuilderProgram>, jsDocParsingMode?: ts.JSDocParsingMode) {
super(sys, options, setParentNodes, jsDocParsingMode);
this.createProgram = createProgram || ts.createEmitAndSemanticDiagnosticsBuilderProgram as unknown as ts.CreateProgram<ts.BuilderProgram>;
}

static create(sys: System | vfs.FileSystem, options?: ts.CompilerOptions, setParentNodes?: boolean, createProgram?: ts.CreateProgram<ts.BuilderProgram>, jsDocParsingMode?: ts.JSDocParsingMode) {
const host = new SolutionBuilderHost(sys, options, setParentNodes, createProgram, jsDocParsingMode);
patchHostForBuildInfoReadWrite(host.sys);
return host;
}

createHash(data: string) {
return `${ts.generateDjb2Hash(data)}-${data}`;
}

diagnostics: SolutionBuilderDiagnostic[] = [];

reportDiagnostic(diagnostic: ts.Diagnostic) {
this.diagnostics.push({ kind: DiagnosticKind.Error, diagnostic });
}

reportSolutionBuilderStatus(diagnostic: ts.Diagnostic) {
this.diagnostics.push({ kind: DiagnosticKind.Status, diagnostic });
}

clearDiagnostics() {
this.diagnostics.length = 0;
}

assertDiagnosticMessages(...expectedDiagnostics: ExpectedDiagnostic[]) {
const actual = this.diagnostics.slice().map(diagnosticToText);
const expected = expectedDiagnostics.map(expectedDiagnosticToText);
assert.deepEqual(
actual,
expected,
`Diagnostic arrays did not match:
Actual: ${JSON.stringify(actual, /*replacer*/ undefined, " ")}
Expected: ${JSON.stringify(expected, /*replacer*/ undefined, " ")}`,
);
}

assertErrors(...expectedDiagnostics: ExpectedErrorDiagnostic[]) {
const actual = this.diagnostics.filter(d => d.kind === DiagnosticKind.Error).map(diagnosticToText);
const expected = expectedDiagnostics.map(expectedDiagnosticToText);
assert.deepEqual(
actual,
expected,
`Diagnostics arrays did not match:
Actual: ${JSON.stringify(actual, /*replacer*/ undefined, " ")}
Expected: ${JSON.stringify(expected, /*replacer*/ undefined, " ")}
Actual All:: ${JSON.stringify(this.diagnostics.slice().map(diagnosticToText), /*replacer*/ undefined, " ")}`,
);
}

printDiagnostics(header = "== Diagnostics ==") {
const out = ts.createDiagnosticReporter(ts.sys);
ts.sys.write(header + "\r\n");
for (const { diagnostic } of this.diagnostics) {
out(diagnostic);
}
}

now() {
return this.sys.now();
}
}
36 changes: 32 additions & 4 deletions src/harness/fourslashImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ import * as vpath from "./_namespaces/vpath.js";
import { LoggerWithInMemoryLogs } from "./tsserverLogger.js";

import ArrayOrSingle = FourSlashInterface.ArrayOrSingle;
import {
harnessSessionLibLocation,
harnessTypingInstallerCacheLocation,
} from "./harnessLanguageService.js";
import { ensureWatchablePath } from "./watchUtils.js";

export const enum FourSlashTestType {
Native,
Expand Down Expand Up @@ -318,7 +323,17 @@ export class TestState {
let startResolveFileRef: FourSlashFile | undefined;

let configFileName: string | undefined;
if (testData.symlinks && this.testType === FourSlashTestType.Server) {
for (const symlink of ts.getOwnKeys(testData.symlinks)) {
ensureWatchablePath(ts.getDirectoryPath(symlink), `Directory of input link: ${symlink}`);
const target = (testData.symlinks[symlink] as vfs.Symlink).symlink;
ensureWatchablePath(ts.getDirectoryPath(target), `Directory of target link: ${target} for symlink ${symlink}`);
}
}
for (const file of testData.files) {
if (this.testType === FourSlashTestType.Server) {
ensureWatchablePath(ts.getDirectoryPath(file.fileName), `Directory of input file: ${file.fileName}`);
}
// Create map between fileName and its content for easily looking up when resolveReference flag is specified
this.inputFiles.set(file.fileName, file.content);
if (isConfig(file)) {
Expand Down Expand Up @@ -348,6 +363,11 @@ export class TestState {
}
}

const libName = (name: string) =>
this.testType !== FourSlashTestType.Server ?
name :
`${harnessSessionLibLocation}/${name}`;

let configParseResult: ts.ParsedCommandLine | undefined;
if (configFileName) {
const baseDir = ts.normalizePath(ts.getDirectoryPath(configFileName));
Expand Down Expand Up @@ -401,13 +421,21 @@ export class TestState {

// Check if no-default-lib flag is false and if so add default library
if (!resolvedResult.isLibFile) {
this.languageServiceAdapterHost.addScript(Harness.Compiler.defaultLibFileName, Harness.Compiler.getDefaultLibrarySourceFile()!.text, /*isRootFile*/ false);
this.languageServiceAdapterHost.addScript(
libName(Harness.Compiler.defaultLibFileName),
Harness.Compiler.getDefaultLibrarySourceFile()!.text,
/*isRootFile*/ false,
);

compilationOptions.lib?.forEach(fileName => {
const libFile = Harness.Compiler.getDefaultLibrarySourceFile(fileName);
ts.Debug.assertIsDefined(libFile, `Could not find lib file '${fileName}'`);
if (libFile) {
this.languageServiceAdapterHost.addScript(fileName, libFile.text, /*isRootFile*/ false);
this.languageServiceAdapterHost.addScript(
libName(fileName),
libFile.text,
/*isRootFile*/ false,
);
}
});
}
Expand All @@ -419,7 +447,7 @@ export class TestState {
// all files if config file not specified, otherwise root files from the config and typings cache files are root files
const isRootFile = !configParseResult ||
ts.contains(configParseResult.fileNames, fileName) ||
(ts.isDeclarationFileName(fileName) && ts.containsPath("/Library/Caches/typescript", fileName));
(ts.isDeclarationFileName(fileName) && ts.containsPath(harnessTypingInstallerCacheLocation, fileName));
this.languageServiceAdapterHost.addScript(fileName, file, isRootFile);
}
});
Expand All @@ -431,7 +459,7 @@ export class TestState {
seen.add(fileName);
const libFile = Harness.Compiler.getDefaultLibrarySourceFile(fileName);
ts.Debug.assertIsDefined(libFile, `Could not find lib file '${fileName}'`);
this.languageServiceAdapterHost.addScript(fileName, libFile.text, /*isRootFile*/ false);
this.languageServiceAdapterHost.addScript(libName(fileName), libFile.text, /*isRootFile*/ false);
if (!ts.some(libFile.libReferenceDirectives)) return;
for (const directive of libFile.libReferenceDirectives) {
addSourceFile(`lib.${directive.fileName}.d.ts`);
Expand Down
Loading

0 comments on commit 7976d9c

Please sign in to comment.