Skip to content

Commit 7976d9c

Browse files
authored
Refactor tsserver, tsc and fourslash-server tests so that paths are always watchable (microsoft#59844)
1 parent 29d92ed commit 7976d9c

File tree

2,663 files changed

+281347
-251727
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

2,663 files changed

+281347
-251727
lines changed

src/compiler/utilitiesPublic.ts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -303,28 +303,34 @@ export function sortAndDeduplicateDiagnostics<T extends Diagnostic>(diagnostics:
303303
return sortAndDeduplicate<T>(diagnostics, compareDiagnostics, diagnosticsEqualityComparer);
304304
}
305305

306+
/** @internal */
307+
export const targetToLibMap = new Map<ScriptTarget, string>([
308+
[ScriptTarget.ESNext, "lib.esnext.full.d.ts"],
309+
[ScriptTarget.ES2023, "lib.es2023.full.d.ts"],
310+
[ScriptTarget.ES2022, "lib.es2022.full.d.ts"],
311+
[ScriptTarget.ES2021, "lib.es2021.full.d.ts"],
312+
[ScriptTarget.ES2020, "lib.es2020.full.d.ts"],
313+
[ScriptTarget.ES2019, "lib.es2019.full.d.ts"],
314+
[ScriptTarget.ES2018, "lib.es2018.full.d.ts"],
315+
[ScriptTarget.ES2017, "lib.es2017.full.d.ts"],
316+
[ScriptTarget.ES2016, "lib.es2016.full.d.ts"],
317+
[ScriptTarget.ES2015, "lib.es6.d.ts"], // We don't use lib.es2015.full.d.ts due to breaking change.
318+
]);
319+
306320
export function getDefaultLibFileName(options: CompilerOptions): string {
307-
switch (getEmitScriptTarget(options)) {
321+
const target = getEmitScriptTarget(options);
322+
switch (target) {
308323
case ScriptTarget.ESNext:
309-
return "lib.esnext.full.d.ts";
310324
case ScriptTarget.ES2023:
311-
return "lib.es2023.full.d.ts";
312325
case ScriptTarget.ES2022:
313-
return "lib.es2022.full.d.ts";
314326
case ScriptTarget.ES2021:
315-
return "lib.es2021.full.d.ts";
316327
case ScriptTarget.ES2020:
317-
return "lib.es2020.full.d.ts";
318328
case ScriptTarget.ES2019:
319-
return "lib.es2019.full.d.ts";
320329
case ScriptTarget.ES2018:
321-
return "lib.es2018.full.d.ts";
322330
case ScriptTarget.ES2017:
323-
return "lib.es2017.full.d.ts";
324331
case ScriptTarget.ES2016:
325-
return "lib.es2016.full.d.ts";
326332
case ScriptTarget.ES2015:
327-
return "lib.es6.d.ts"; // We don't use lib.es2015.full.d.ts due to breaking change.
333+
return targetToLibMap.get(target)!;
328334
default:
329335
return "lib.d.ts";
330336
}

src/harness/fakesHosts.ts

Lines changed: 0 additions & 215 deletions
Original file line numberDiff line numberDiff line change
@@ -422,218 +422,3 @@ export class CompilerHost implements ts.CompilerHost {
422422
return parsed;
423423
}
424424
}
425-
426-
export type ExpectedDiagnosticMessage = [ts.DiagnosticMessage, ...(string | number)[]];
427-
export interface ExpectedDiagnosticMessageChain {
428-
message: ExpectedDiagnosticMessage;
429-
next?: ExpectedDiagnosticMessageChain[];
430-
}
431-
432-
export interface ExpectedDiagnosticLocation {
433-
file: string;
434-
start: number;
435-
length: number;
436-
}
437-
export interface ExpectedDiagnosticRelatedInformation extends ExpectedDiagnosticMessageChain {
438-
location?: ExpectedDiagnosticLocation;
439-
}
440-
441-
export enum DiagnosticKind {
442-
Error = "Error",
443-
Status = "Status",
444-
}
445-
export interface ExpectedErrorDiagnostic extends ExpectedDiagnosticRelatedInformation {
446-
relatedInformation?: ExpectedDiagnosticRelatedInformation[];
447-
}
448-
449-
export type ExpectedDiagnostic = ExpectedDiagnosticMessage | ExpectedErrorDiagnostic;
450-
451-
interface SolutionBuilderDiagnostic {
452-
kind: DiagnosticKind;
453-
diagnostic: ts.Diagnostic;
454-
}
455-
456-
function indentedText(indent: number, text: string) {
457-
if (!indent) return text;
458-
let indentText = "";
459-
for (let i = 0; i < indent; i++) {
460-
indentText += " ";
461-
}
462-
return `
463-
${indentText}${text}`;
464-
}
465-
466-
function expectedDiagnosticMessageToText([message, ...args]: ExpectedDiagnosticMessage) {
467-
let text = ts.getLocaleSpecificMessage(message);
468-
if (args.length) {
469-
text = ts.formatStringFromArgs(text, args);
470-
}
471-
return text;
472-
}
473-
474-
function expectedDiagnosticMessageChainToText({ message, next }: ExpectedDiagnosticMessageChain, indent = 0) {
475-
let text = indentedText(indent, expectedDiagnosticMessageToText(message));
476-
if (next) {
477-
indent++;
478-
next.forEach(kid => text += expectedDiagnosticMessageChainToText(kid, indent));
479-
}
480-
return text;
481-
}
482-
483-
function expectedDiagnosticRelatedInformationToText({ location, ...diagnosticMessage }: ExpectedDiagnosticRelatedInformation) {
484-
const text = expectedDiagnosticMessageChainToText(diagnosticMessage);
485-
if (location) {
486-
const { file, start, length } = location;
487-
return `${file}(${start}:${length}):: ${text}`;
488-
}
489-
return text;
490-
}
491-
492-
function expectedErrorDiagnosticToText({ relatedInformation, ...diagnosticRelatedInformation }: ExpectedErrorDiagnostic) {
493-
let text = `${DiagnosticKind.Error}!: ${expectedDiagnosticRelatedInformationToText(diagnosticRelatedInformation)}`;
494-
if (relatedInformation) {
495-
for (const kid of relatedInformation) {
496-
text += `
497-
related:: ${expectedDiagnosticRelatedInformationToText(kid)}`;
498-
}
499-
}
500-
return text;
501-
}
502-
503-
function expectedDiagnosticToText(errorOrStatus: ExpectedDiagnostic) {
504-
return ts.isArray(errorOrStatus) ?
505-
`${DiagnosticKind.Status}!: ${expectedDiagnosticMessageToText(errorOrStatus)}` :
506-
expectedErrorDiagnosticToText(errorOrStatus);
507-
}
508-
509-
function diagnosticMessageChainToText({ messageText, next }: ts.DiagnosticMessageChain, indent = 0) {
510-
let text = indentedText(indent, messageText);
511-
if (next) {
512-
indent++;
513-
next.forEach(kid => text += diagnosticMessageChainToText(kid, indent));
514-
}
515-
return text;
516-
}
517-
518-
function diagnosticRelatedInformationToText({ file, start, length, messageText }: ts.DiagnosticRelatedInformation) {
519-
const text = typeof messageText === "string" ?
520-
messageText :
521-
diagnosticMessageChainToText(messageText);
522-
return file ?
523-
`${file.fileName}(${start}:${length}):: ${text}` :
524-
text;
525-
}
526-
527-
function diagnosticToText({ kind, diagnostic: { relatedInformation, ...diagnosticRelatedInformation } }: SolutionBuilderDiagnostic) {
528-
let text = `${kind}!: ${diagnosticRelatedInformationToText(diagnosticRelatedInformation)}`;
529-
if (relatedInformation) {
530-
for (const kid of relatedInformation) {
531-
text += `
532-
related:: ${diagnosticRelatedInformationToText(kid)}`;
533-
}
534-
}
535-
return text;
536-
}
537-
538-
export const version = "FakeTSVersion";
539-
540-
export function patchHostForBuildInfoReadWrite<T extends ts.System>(sys: T) {
541-
const originalReadFile = sys.readFile;
542-
sys.readFile = (path, encoding) => {
543-
const value = originalReadFile.call(sys, path, encoding);
544-
if (!value || !ts.isBuildInfoFile(path)) return value;
545-
const buildInfo = ts.getBuildInfo(path, value);
546-
if (!buildInfo) return value;
547-
ts.Debug.assert(buildInfo.version === version);
548-
buildInfo.version = ts.version;
549-
return ts.getBuildInfoText(buildInfo);
550-
};
551-
return patchHostForBuildInfoWrite(sys, version);
552-
}
553-
554-
export function patchHostForBuildInfoWrite<T extends ts.System>(sys: T, version: string) {
555-
const originalWrite = sys.write;
556-
sys.write = msg => originalWrite.call(sys, msg.replace(ts.version, version));
557-
const originalWriteFile = sys.writeFile;
558-
sys.writeFile = (fileName: string, content: string, writeByteOrderMark: boolean) => {
559-
if (ts.isBuildInfoFile(fileName)) {
560-
const buildInfo = ts.getBuildInfo(fileName, content);
561-
if (buildInfo) {
562-
buildInfo.version = version;
563-
return originalWriteFile.call(sys, fileName, ts.getBuildInfoText(buildInfo), writeByteOrderMark);
564-
}
565-
}
566-
return originalWriteFile.call(sys, fileName, content, writeByteOrderMark);
567-
};
568-
return sys;
569-
}
570-
571-
export class SolutionBuilderHost extends CompilerHost implements ts.SolutionBuilderHost<ts.BuilderProgram> {
572-
createProgram: ts.CreateProgram<ts.BuilderProgram>;
573-
574-
private constructor(sys: System | vfs.FileSystem, options?: ts.CompilerOptions, setParentNodes?: boolean, createProgram?: ts.CreateProgram<ts.BuilderProgram>, jsDocParsingMode?: ts.JSDocParsingMode) {
575-
super(sys, options, setParentNodes, jsDocParsingMode);
576-
this.createProgram = createProgram || ts.createEmitAndSemanticDiagnosticsBuilderProgram as unknown as ts.CreateProgram<ts.BuilderProgram>;
577-
}
578-
579-
static create(sys: System | vfs.FileSystem, options?: ts.CompilerOptions, setParentNodes?: boolean, createProgram?: ts.CreateProgram<ts.BuilderProgram>, jsDocParsingMode?: ts.JSDocParsingMode) {
580-
const host = new SolutionBuilderHost(sys, options, setParentNodes, createProgram, jsDocParsingMode);
581-
patchHostForBuildInfoReadWrite(host.sys);
582-
return host;
583-
}
584-
585-
createHash(data: string) {
586-
return `${ts.generateDjb2Hash(data)}-${data}`;
587-
}
588-
589-
diagnostics: SolutionBuilderDiagnostic[] = [];
590-
591-
reportDiagnostic(diagnostic: ts.Diagnostic) {
592-
this.diagnostics.push({ kind: DiagnosticKind.Error, diagnostic });
593-
}
594-
595-
reportSolutionBuilderStatus(diagnostic: ts.Diagnostic) {
596-
this.diagnostics.push({ kind: DiagnosticKind.Status, diagnostic });
597-
}
598-
599-
clearDiagnostics() {
600-
this.diagnostics.length = 0;
601-
}
602-
603-
assertDiagnosticMessages(...expectedDiagnostics: ExpectedDiagnostic[]) {
604-
const actual = this.diagnostics.slice().map(diagnosticToText);
605-
const expected = expectedDiagnostics.map(expectedDiagnosticToText);
606-
assert.deepEqual(
607-
actual,
608-
expected,
609-
`Diagnostic arrays did not match:
610-
Actual: ${JSON.stringify(actual, /*replacer*/ undefined, " ")}
611-
Expected: ${JSON.stringify(expected, /*replacer*/ undefined, " ")}`,
612-
);
613-
}
614-
615-
assertErrors(...expectedDiagnostics: ExpectedErrorDiagnostic[]) {
616-
const actual = this.diagnostics.filter(d => d.kind === DiagnosticKind.Error).map(diagnosticToText);
617-
const expected = expectedDiagnostics.map(expectedDiagnosticToText);
618-
assert.deepEqual(
619-
actual,
620-
expected,
621-
`Diagnostics arrays did not match:
622-
Actual: ${JSON.stringify(actual, /*replacer*/ undefined, " ")}
623-
Expected: ${JSON.stringify(expected, /*replacer*/ undefined, " ")}
624-
Actual All:: ${JSON.stringify(this.diagnostics.slice().map(diagnosticToText), /*replacer*/ undefined, " ")}`,
625-
);
626-
}
627-
628-
printDiagnostics(header = "== Diagnostics ==") {
629-
const out = ts.createDiagnosticReporter(ts.sys);
630-
ts.sys.write(header + "\r\n");
631-
for (const { diagnostic } of this.diagnostics) {
632-
out(diagnostic);
633-
}
634-
}
635-
636-
now() {
637-
return this.sys.now();
638-
}
639-
}

src/harness/fourslashImpl.ts

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ import * as vpath from "./_namespaces/vpath.js";
99
import { LoggerWithInMemoryLogs } from "./tsserverLogger.js";
1010

1111
import ArrayOrSingle = FourSlashInterface.ArrayOrSingle;
12+
import {
13+
harnessSessionLibLocation,
14+
harnessTypingInstallerCacheLocation,
15+
} from "./harnessLanguageService.js";
16+
import { ensureWatchablePath } from "./watchUtils.js";
1217

1318
export const enum FourSlashTestType {
1419
Native,
@@ -318,7 +323,17 @@ export class TestState {
318323
let startResolveFileRef: FourSlashFile | undefined;
319324

320325
let configFileName: string | undefined;
326+
if (testData.symlinks && this.testType === FourSlashTestType.Server) {
327+
for (const symlink of ts.getOwnKeys(testData.symlinks)) {
328+
ensureWatchablePath(ts.getDirectoryPath(symlink), `Directory of input link: ${symlink}`);
329+
const target = (testData.symlinks[symlink] as vfs.Symlink).symlink;
330+
ensureWatchablePath(ts.getDirectoryPath(target), `Directory of target link: ${target} for symlink ${symlink}`);
331+
}
332+
}
321333
for (const file of testData.files) {
334+
if (this.testType === FourSlashTestType.Server) {
335+
ensureWatchablePath(ts.getDirectoryPath(file.fileName), `Directory of input file: ${file.fileName}`);
336+
}
322337
// Create map between fileName and its content for easily looking up when resolveReference flag is specified
323338
this.inputFiles.set(file.fileName, file.content);
324339
if (isConfig(file)) {
@@ -348,6 +363,11 @@ export class TestState {
348363
}
349364
}
350365

366+
const libName = (name: string) =>
367+
this.testType !== FourSlashTestType.Server ?
368+
name :
369+
`${harnessSessionLibLocation}/${name}`;
370+
351371
let configParseResult: ts.ParsedCommandLine | undefined;
352372
if (configFileName) {
353373
const baseDir = ts.normalizePath(ts.getDirectoryPath(configFileName));
@@ -401,13 +421,21 @@ export class TestState {
401421

402422
// Check if no-default-lib flag is false and if so add default library
403423
if (!resolvedResult.isLibFile) {
404-
this.languageServiceAdapterHost.addScript(Harness.Compiler.defaultLibFileName, Harness.Compiler.getDefaultLibrarySourceFile()!.text, /*isRootFile*/ false);
424+
this.languageServiceAdapterHost.addScript(
425+
libName(Harness.Compiler.defaultLibFileName),
426+
Harness.Compiler.getDefaultLibrarySourceFile()!.text,
427+
/*isRootFile*/ false,
428+
);
405429

406430
compilationOptions.lib?.forEach(fileName => {
407431
const libFile = Harness.Compiler.getDefaultLibrarySourceFile(fileName);
408432
ts.Debug.assertIsDefined(libFile, `Could not find lib file '${fileName}'`);
409433
if (libFile) {
410-
this.languageServiceAdapterHost.addScript(fileName, libFile.text, /*isRootFile*/ false);
434+
this.languageServiceAdapterHost.addScript(
435+
libName(fileName),
436+
libFile.text,
437+
/*isRootFile*/ false,
438+
);
411439
}
412440
});
413441
}
@@ -419,7 +447,7 @@ export class TestState {
419447
// all files if config file not specified, otherwise root files from the config and typings cache files are root files
420448
const isRootFile = !configParseResult ||
421449
ts.contains(configParseResult.fileNames, fileName) ||
422-
(ts.isDeclarationFileName(fileName) && ts.containsPath("/Library/Caches/typescript", fileName));
450+
(ts.isDeclarationFileName(fileName) && ts.containsPath(harnessTypingInstallerCacheLocation, fileName));
423451
this.languageServiceAdapterHost.addScript(fileName, file, isRootFile);
424452
}
425453
});
@@ -431,7 +459,7 @@ export class TestState {
431459
seen.add(fileName);
432460
const libFile = Harness.Compiler.getDefaultLibrarySourceFile(fileName);
433461
ts.Debug.assertIsDefined(libFile, `Could not find lib file '${fileName}'`);
434-
this.languageServiceAdapterHost.addScript(fileName, libFile.text, /*isRootFile*/ false);
462+
this.languageServiceAdapterHost.addScript(libName(fileName), libFile.text, /*isRootFile*/ false);
435463
if (!ts.some(libFile.libReferenceDirectives)) return;
436464
for (const directive of libFile.libReferenceDirectives) {
437465
addSourceFile(`lib.${directive.fileName}.d.ts`);

0 commit comments

Comments
 (0)