Skip to content

Commit 6cb3065

Browse files
committed
Test to verify presence of .d.ts file
1 parent 3dc0d5a commit 6cb3065

File tree

2 files changed

+104
-24
lines changed

2 files changed

+104
-24
lines changed

src/compiler/program.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -787,7 +787,13 @@ namespace ts {
787787
// Key is a file name. Value is the (non-empty, or undefined) list of files that redirect to it.
788788
let redirectTargetsMap = createMultiMap<string>();
789789

790-
const filesByName = createMap<SourceFile | undefined>();
790+
/**
791+
* map with
792+
* - SourceFile if present
793+
* - false if sourceFile missing for source of project reference redirect
794+
* - undefined otherwise
795+
*/
796+
const filesByName = createMap<SourceFile | false | undefined>();
791797
let missingFilePaths: ReadonlyArray<Path> | undefined;
792798
// stores 'filename -> file association' ignoring case
793799
// used to track cases when two file names differ only in casing
@@ -854,7 +860,7 @@ namespace ts {
854860
}
855861
}
856862

857-
missingFilePaths = arrayFrom(filesByName.keys(), p => <Path>p).filter(p => !filesByName.get(p));
863+
missingFilePaths = arrayFrom(mapDefinedIterator(filesByName.entries(), ([path, file]) => file === undefined ? path as Path : undefined));
858864
files = stableSort(processingDefaultLibFiles, compareDefaultLibFiles).concat(processingOtherFiles);
859865
processingDefaultLibFiles = undefined;
860866
processingOtherFiles = undefined;
@@ -1567,7 +1573,7 @@ namespace ts {
15671573
}
15681574

15691575
function getSourceFileByPath(path: Path): SourceFile | undefined {
1570-
return filesByName.get(path);
1576+
return filesByName.get(path) || undefined;
15711577
}
15721578

15731579
function getDiagnosticsHelper<T extends Diagnostic>(
@@ -2104,7 +2110,7 @@ namespace ts {
21042110

21052111
/** This should have similar behavior to 'processSourceFile' without diagnostics or mutation. */
21062112
function getSourceFileFromReference(referencingFile: SourceFile, ref: FileReference): SourceFile | undefined {
2107-
return getSourceFileFromReferenceWorker(resolveTripleslashReference(ref.fileName, referencingFile.fileName), fileName => filesByName.get(toPath(fileName)));
2113+
return getSourceFileFromReferenceWorker(resolveTripleslashReference(ref.fileName, referencingFile.fileName), fileName => filesByName.get(toPath(fileName)) || undefined);
21082114
}
21092115

21102116
function getSourceFileFromReferenceWorker(
@@ -2235,7 +2241,7 @@ namespace ts {
22352241
}
22362242
}
22372243

2238-
return file;
2244+
return file || undefined;
22392245
}
22402246

22412247
let redirectedPath: Path | undefined;
@@ -2327,9 +2333,12 @@ namespace ts {
23272333
}
23282334

23292335
function addFileToFilesByName(file: SourceFile | undefined, path: Path, redirectedPath: Path | undefined) {
2330-
filesByName.set(path, file);
23312336
if (redirectedPath) {
23322337
filesByName.set(redirectedPath, file);
2338+
filesByName.set(path, file || false);
2339+
}
2340+
else {
2341+
filesByName.set(path, file);
23332342
}
23342343
}
23352344

src/testRunner/unittests/tsserverProjectSystem.ts

Lines changed: 89 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10692,7 +10692,11 @@ export function fn5() { }
1069210692
const mainTs: File = {
1069310693
path: `${mainLocation}/main.ts`,
1069410694
content: `import {
10695-
fn1, fn2, fn3, fn4, fn5
10695+
fn1,
10696+
fn2,
10697+
fn3,
10698+
fn4,
10699+
fn5
1069610700
} from '../dependency/fns'
1069710701

1069810702
fn1();
@@ -10739,9 +10743,9 @@ fn5();
1073910743
}
1074010744

1074110745
// Returns request and expected Response, expected response when no map file
10742-
type SessionAction<Req = protocol.Request, Response = {}> = [Partial<Req>, Response, Response?];
10746+
type SessionAction<Req = protocol.Request, Response = {}> = [Partial<Req>, Response, Response?, Response?];
1074310747
function gotoDefintinionFromMainTs(fn: number): SessionAction<protocol.DefinitionAndBoundSpanRequest, protocol.DefinitionInfoAndBoundSpan> {
10744-
const startSpan = { line: fn + 4, offset: 1 };
10748+
const startSpan = { line: fn + 8, offset: 1 };
1074510749
const textSpan: protocol.TextSpan = { start: startSpan, end: { line: startSpan.line, offset: startSpan.offset + 3 } };
1074610750
const definitionSpan: protocol.FileSpan = { file: dependencyTs.path, start: { line: fn, offset: 17 }, end: { line: fn, offset: 20 } };
1074710751
const declareSpaceLength = "declare ".length;
@@ -10751,12 +10755,19 @@ fn5();
1075110755
arguments: { file: mainTs.path, ...startSpan }
1075210756
},
1075310757
{
10758+
// To dependency
1075410759
definitions: [definitionSpan],
1075510760
textSpan
1075610761
},
1075710762
{
10763+
// To the dts
1075810764
definitions: [{ file: dtsPath, start: { line: fn, offset: definitionSpan.start.offset + declareSpaceLength }, end: { line: fn, offset: definitionSpan.end.offset + declareSpaceLength } }],
1075910765
textSpan
10766+
},
10767+
{
10768+
// To import declaration
10769+
definitions: [{ file: mainTs.path, start: { line: fn + 1, offset: 5 }, end: { line: fn + 1, offset: 8 } }],
10770+
textSpan
1076010771
}
1076110772
];
1076210773
}
@@ -10816,11 +10827,16 @@ fn5();
1081610827
return { host, session };
1081710828
}
1081810829

10819-
function checkProject(session: TestSession) {
10830+
function checkProject(session: TestSession, noDts?: true) {
1082010831
const service = session.getProjectService();
1082110832
checkNumberOfProjects(service, { configuredProjects: 1 + verifier.length });
1082210833
configFiles.forEach((configFile, index) => {
10823-
checkProjectActualFiles(service.configuredProjects.get(configFile)!, expectedProjectActualFiles[index]);
10834+
checkProjectActualFiles(
10835+
service.configuredProjects.get(configFile)!,
10836+
noDts ?
10837+
expectedProjectActualFiles[index].filter(f => f.toLowerCase() !== dtsPath) :
10838+
expectedProjectActualFiles[index]
10839+
);
1082410840
});
1082510841
}
1082610842

@@ -10839,6 +10855,21 @@ fn5();
1083910855
);
1084010856
}
1084110857

10858+
function verifyInfosWhenNoDtsFile(session: TestSession, host: TestServerHost) {
10859+
const dtsMapClosedInfo = firstDefined(closedInfos, f => f.toLowerCase() === dtsMapPath ? f : undefined);
10860+
const dtsClosedInfo = firstDefined(closedInfos, f => f.toLowerCase() === dtsPath ? f : undefined);
10861+
verifyInfosWithRandom(
10862+
session,
10863+
host,
10864+
openInfos,
10865+
closedInfos.filter(f => f !== dtsMapClosedInfo && f !== dtsClosedInfo && f !== dependencyTs.path),
10866+
// When project actual file contains dts, it needs to be watched
10867+
dtsClosedInfo && verifier.length === 1 && expectedProjectActualFiles[0].some(f => f.toLowerCase() === dtsPath) ?
10868+
otherWatchedFiles.concat(dtsClosedInfo) :
10869+
otherWatchedFiles
10870+
);
10871+
}
10872+
1084210873
function verifyDocumentPositionMapper(session: TestSession, dependencyMap: server.ScriptInfo, documentPositionMapper: server.ScriptInfo["documentPositionMapper"], notEqual?: true) {
1084310874
assert.strictEqual(session.getProjectService().filenameToScriptInfo.get(dtsMapPath), dependencyMap);
1084410875
if (notEqual) {
@@ -10850,24 +10881,29 @@ fn5();
1085010881
}
1085110882

1085210883
function action(actionGetter: SessionActionGetter, fn: number, session: TestSession) {
10853-
const [req, expectedResponse, expectedNoMapResponse] = actionGetter(fn);
10884+
const [req, expectedResponse, expectedNoMapResponse, expectedNoDtsResponse] = actionGetter(fn);
1085410885
const { response } = session.executeCommandSeq(req);
10855-
return { response, expectedResponse, expectedNoMapResponse };
10886+
return { response, expectedResponse, expectedNoMapResponse, expectedNoDtsResponse };
1085610887
}
1085710888

1085810889
function firstAction(session: TestSession) {
1085910890
actionGetters.forEach(actionGetter => action(actionGetter, 1, session));
1086010891
}
1086110892

10862-
function verifyAllFnActionWorker(session: TestSession, verifyAction: (result: ReturnType<typeof action>, dtsInfo: server.ScriptInfo, isFirst: boolean) => void) {
10893+
function verifyAllFnActionWorker(session: TestSession, verifyAction: (result: ReturnType<typeof action>, dtsInfo: server.ScriptInfo | undefined, isFirst: boolean) => void, dtsAbsent?: true) {
1086310894
// action
1086410895
let isFirst = true;
1086510896
for (const actionGetter of actionGetters) {
1086610897
for (let fn = 1; fn <= 5; fn++) {
1086710898
const result = action(actionGetter, fn, session);
1086810899
const dtsInfo = session.getProjectService().filenameToScriptInfo.get(dtsPath);
10869-
assert.isDefined(dtsInfo);
10870-
verifyAction(result, dtsInfo!, isFirst);
10900+
if (dtsAbsent) {
10901+
assert.isUndefined(dtsInfo);
10902+
}
10903+
else {
10904+
assert.isDefined(dtsInfo);
10905+
}
10906+
verifyAction(result, dtsInfo, isFirst);
1087110907
isFirst = false;
1087210908
}
1087310909
}
@@ -10884,7 +10920,7 @@ fn5();
1088410920
verifyAllFnActionWorker(session, ({ response, expectedResponse }, dtsInfo, isFirst) => {
1088510921
assert.deepEqual(response, expectedResponse);
1088610922
verifyInfos(session, host);
10887-
assert.equal(dtsInfo.sourceMapFilePath, dtsMapPath);
10923+
assert.equal(dtsInfo!.sourceMapFilePath, dtsMapPath);
1088810924
if (isFirst) {
1088910925
if (dependencyMap) {
1089010926
verifyDocumentPositionMapper(session, dependencyMap, documentPositionMapper, firstDocumentPositionMapperNotEquals);
@@ -10910,22 +10946,33 @@ fn5();
1091010946
let sourceMapFilePath: server.ScriptInfo["sourceMapFilePath"];
1091110947
// action
1091210948
verifyAllFnActionWorker(session, ({ response, expectedResponse, expectedNoMapResponse }, dtsInfo, isFirst) => {
10913-
assert.deepEqual(response, expectedNoMapResponse && verifier.length ? expectedNoMapResponse : expectedResponse);
10949+
assert.deepEqual(response, expectedNoMapResponse || expectedResponse);
1091410950
verifyInfosWhenNoMapFile(session, host, dependencyTsOK);
1091510951
assert.isUndefined(session.getProjectService().filenameToScriptInfo.get(dtsMapPath));
1091610952
if (isFirst) {
10917-
assert.isNotString(dtsInfo.sourceMapFilePath);
10918-
assert.isNotFalse(dtsInfo.sourceMapFilePath);
10919-
assert.isDefined(dtsInfo.sourceMapFilePath);
10920-
sourceMapFilePath = dtsInfo.sourceMapFilePath;
10953+
assert.isNotString(dtsInfo!.sourceMapFilePath);
10954+
assert.isNotFalse(dtsInfo!.sourceMapFilePath);
10955+
assert.isDefined(dtsInfo!.sourceMapFilePath);
10956+
sourceMapFilePath = dtsInfo!.sourceMapFilePath;
1092110957
}
1092210958
else {
10923-
assert.equal(dtsInfo.sourceMapFilePath, sourceMapFilePath);
10959+
assert.equal(dtsInfo!.sourceMapFilePath, sourceMapFilePath);
1092410960
}
1092510961
});
1092610962
return sourceMapFilePath;
1092710963
}
1092810964

10965+
function verifyAllFnActionWithNoDts(
10966+
session: TestSession,
10967+
host: TestServerHost
10968+
) {
10969+
// action
10970+
verifyAllFnActionWorker(session, ({ response, expectedResponse, expectedNoDtsResponse }) => {
10971+
assert.deepEqual(response, expectedNoDtsResponse || expectedResponse);
10972+
verifyInfosWhenNoDtsFile(session, host);
10973+
}, /*dtsAbsent*/ true);
10974+
}
10975+
1092910976
function verifyScenarioWithChangesWorker(
1093010977
change: (host: TestServerHost, session: TestSession) => void,
1093110978
afterActionDocumentPositionMapperNotEquals: true | undefined,
@@ -10996,6 +11043,21 @@ fn5();
1099611043
verifyOnlyRandomInfos(session, host);
1099711044
}
1099811045

11046+
function verifyMainScenarioAndScriptInfoCollectionWithNoDts(session: TestSession, host: TestServerHost) {
11047+
// Main scenario action
11048+
verifyAllFnActionWithNoDts(session, host);
11049+
11050+
// Collecting at this point retains dependency.d.ts and map watcher
11051+
closeFilesForSession([randomFile], session);
11052+
openFilesForSession([randomFile], session);
11053+
verifyInfosWhenNoDtsFile(session, host);
11054+
11055+
// Closing open file, removes dependencies too
11056+
closeFilesForSession([...openFiles, randomFile], session);
11057+
openFilesForSession([randomFile], session);
11058+
verifyOnlyRandomInfos(session, host);
11059+
}
11060+
1099911061
it(mainScenario, () => {
1100011062
const { host, session } = openTsFile();
1100111063
checkProject(session);
@@ -11060,13 +11122,22 @@ fn5();
1106011122
verifyMainScenarioAndScriptInfoCollectionWithNoMap(session, host, /*dependencyTsOKInScenario*/ true);
1106111123
});
1106211124
});
11125+
11126+
describe("when .d.ts file is not present", () => {
11127+
it(mainScenario, () => {
11128+
const { host, session } = openTsFile(host => host.deleteFile(dtsLocation));
11129+
checkProject(session, /*noDts*/ true);
11130+
11131+
verifyMainScenarioAndScriptInfoCollectionWithNoDts(session, host);
11132+
});
11133+
});
1106311134
}
1106411135

1106511136
const usageVerifier: DocumentPositionMapperVerifier = [
1106611137
/*openFile*/ mainTs,
1106711138
/*expectedProjectActualFiles*/[mainTs.path, libFile.path, mainConfig.path, dtsPath],
1106811139
/*actionGetter*/ gotoDefintinionFromMainTs,
11069-
/*openFileLastLine*/ 10
11140+
/*openFileLastLine*/ 14
1107011141
];
1107111142
describe("from project that uses dependency", () => {
1107211143
const closedInfos = [dependencyTs.path, dependencyConfig.path, libFile.path, dtsPath, dtsMapLocation];

0 commit comments

Comments
 (0)