Merge pull request #36383 from uniqueiniquity/redirectInfo

Redirect info
This commit is contained in:
Ben Lichtman
2020-01-28 11:51:06 -08:00
committed by GitHub
6 changed files with 301 additions and 31 deletions
+11 -6
View File
@@ -3124,19 +3124,24 @@ namespace ts.server {
return result;
}
private collectChanges(lastKnownProjectVersions: protocol.ProjectVersionInfo[], currentProjects: Project[], result: ProjectFilesWithTSDiagnostics[]): void {
private collectChanges(
lastKnownProjectVersions: protocol.ProjectVersionInfo[],
currentProjects: Project[],
includeProjectReferenceRedirectInfo: boolean | undefined,
result: ProjectFilesWithTSDiagnostics[]
): void {
for (const proj of currentProjects) {
const knownProject = find(lastKnownProjectVersions, p => p.projectName === proj.getProjectName());
result.push(proj.getChangesSinceVersion(knownProject && knownProject.version));
result.push(proj.getChangesSinceVersion(knownProject && knownProject.version, includeProjectReferenceRedirectInfo));
}
}
/* @internal */
synchronizeProjectList(knownProjects: protocol.ProjectVersionInfo[]): ProjectFilesWithTSDiagnostics[] {
synchronizeProjectList(knownProjects: protocol.ProjectVersionInfo[], includeProjectReferenceRedirectInfo?: boolean): ProjectFilesWithTSDiagnostics[] {
const files: ProjectFilesWithTSDiagnostics[] = [];
this.collectChanges(knownProjects, this.externalProjects, files);
this.collectChanges(knownProjects, arrayFrom(this.configuredProjects.values()), files);
this.collectChanges(knownProjects, this.inferredProjects, files);
this.collectChanges(knownProjects, this.externalProjects, includeProjectReferenceRedirectInfo, files);
this.collectChanges(knownProjects, arrayFrom(this.configuredProjects.values()), includeProjectReferenceRedirectInfo, files);
this.collectChanges(knownProjects, this.inferredProjects, includeProjectReferenceRedirectInfo, files);
return files;
}
+73 -17
View File
@@ -167,7 +167,7 @@ namespace ts.server {
/**
* Set of files that was returned from the last call to getChangesSinceVersion.
*/
private lastReportedFileNames: Map<true> | undefined;
private lastReportedFileNames: Map<boolean> | undefined;
/**
* Last version that was reported.
*/
@@ -803,6 +803,14 @@ namespace ts.server {
return result;
}
/* @internal */
getFileNamesWithRedirectInfo(includeProjectReferenceRedirectInfo: boolean) {
return this.getFileNames().map((fileName): protocol.FileWithProjectReferenceRedirectInfo => ({
fileName,
isSourceOfProjectReferenceRedirect: includeProjectReferenceRedirectInfo && this.isSourceOfProjectReferenceRedirect(fileName)
}));
}
hasConfigFile(configFilePath: NormalizedPath) {
if (this.program && this.languageServiceEnabled) {
const configFile = this.program.getCompilerOptions().configFile;
@@ -1300,7 +1308,15 @@ namespace ts.server {
}
/* @internal */
getChangesSinceVersion(lastKnownVersion?: number): ProjectFilesWithTSDiagnostics {
getChangesSinceVersion(lastKnownVersion?: number, includeProjectReferenceRedirectInfo?: boolean): ProjectFilesWithTSDiagnostics {
const includeProjectReferenceRedirectInfoIfRequested =
includeProjectReferenceRedirectInfo
? (files: Map<boolean>) => arrayFrom(files.entries(), ([fileName, isSourceOfProjectReferenceRedirect]): protocol.FileWithProjectReferenceRedirectInfo => ({
fileName,
isSourceOfProjectReferenceRedirect
}))
: (files: Map<boolean>) => arrayFrom(files.keys());
// Update the graph only if initial configured project load is not pending
if (!this.isInitialLoadPending()) {
updateProjectIfDirty(this);
@@ -1324,35 +1340,75 @@ namespace ts.server {
}
// compute and return the difference
const lastReportedFileNames = this.lastReportedFileNames;
const externalFiles = this.getExternalFiles().map(f => toNormalizedPath(f));
const currentFiles = arrayToSet(this.getFileNames().concat(externalFiles));
const externalFiles = this.getExternalFiles().map((f): protocol.FileWithProjectReferenceRedirectInfo => ({
fileName: toNormalizedPath(f),
isSourceOfProjectReferenceRedirect: false
}));
const currentFiles = arrayToMap(
this.getFileNamesWithRedirectInfo(!!includeProjectReferenceRedirectInfo).concat(externalFiles),
info => info.fileName,
info => info.isSourceOfProjectReferenceRedirect
);
const added: Map<boolean> = new Map<boolean>();
const removed: Map<boolean> = new Map<boolean>();
const added: string[] = [];
const removed: string[] = [];
const updated: string[] = updatedFileNames ? arrayFrom(updatedFileNames.keys()) : [];
const updatedRedirects: protocol.FileWithProjectReferenceRedirectInfo[] = [];
forEachKey(currentFiles, id => {
if (!lastReportedFileNames.has(id)) {
added.push(id);
forEachEntry(currentFiles, (isSourceOfProjectReferenceRedirect, fileName) => {
if (!lastReportedFileNames.has(fileName)) {
added.set(fileName, isSourceOfProjectReferenceRedirect);
}
else if (includeProjectReferenceRedirectInfo && isSourceOfProjectReferenceRedirect !== lastReportedFileNames.get(fileName)){
updatedRedirects.push({
fileName,
isSourceOfProjectReferenceRedirect
});
}
});
forEachKey(lastReportedFileNames, id => {
if (!currentFiles.has(id)) {
removed.push(id);
forEachEntry(lastReportedFileNames, (isSourceOfProjectReferenceRedirect, fileName) => {
if (!currentFiles.has(fileName)) {
removed.set(fileName, isSourceOfProjectReferenceRedirect);
}
});
this.lastReportedFileNames = currentFiles;
this.lastReportedVersion = this.projectProgramVersion;
return { info, changes: { added, removed, updated }, projectErrors: this.getGlobalProjectErrors() };
return {
info,
changes: {
added: includeProjectReferenceRedirectInfoIfRequested(added),
removed: includeProjectReferenceRedirectInfoIfRequested(removed),
updated: includeProjectReferenceRedirectInfoIfRequested
? updated.map((fileName): protocol.FileWithProjectReferenceRedirectInfo => ({
fileName,
isSourceOfProjectReferenceRedirect: this.isSourceOfProjectReferenceRedirect(fileName)
}))
: updated,
updatedRedirects: includeProjectReferenceRedirectInfo ? updatedRedirects : undefined
},
projectErrors: this.getGlobalProjectErrors()
};
}
else {
// unknown version - return everything
const projectFileNames = this.getFileNames();
const externalFiles = this.getExternalFiles().map(f => toNormalizedPath(f));
const projectFileNames = this.getFileNamesWithRedirectInfo(!!includeProjectReferenceRedirectInfo);
const externalFiles = this.getExternalFiles().map((f): protocol.FileWithProjectReferenceRedirectInfo => ({
fileName: toNormalizedPath(f),
isSourceOfProjectReferenceRedirect: false
}));
const allFiles = projectFileNames.concat(externalFiles);
this.lastReportedFileNames = arrayToSet(allFiles);
this.lastReportedFileNames = arrayToMap(
allFiles,
info => info.fileName,
info => info.isSourceOfProjectReferenceRedirect
);
this.lastReportedVersion = this.projectProgramVersion;
return { info, files: allFiles, projectErrors: this.getGlobalProjectErrors() };
return {
info,
files: includeProjectReferenceRedirectInfo ? allFiles : allFiles.map(f => f.fileName),
projectErrors: this.getGlobalProjectErrors()
};
}
}
+28 -4
View File
@@ -1324,6 +1324,18 @@ namespace ts.server.protocol {
lastFileExceededProgramSize?: string;
}
export interface FileWithProjectReferenceRedirectInfo {
/**
* Name of file
*/
fileName: string;
/**
* True if the file is primarily included in a referenced project
*/
isSourceOfProjectReferenceRedirect: boolean;
}
/**
* Represents a set of changes that happen in project
*/
@@ -1331,15 +1343,20 @@ namespace ts.server.protocol {
/**
* List of added files
*/
added: string[];
added: string[] | FileWithProjectReferenceRedirectInfo[];
/**
* List of removed files
*/
removed: string[];
removed: string[] | FileWithProjectReferenceRedirectInfo[];
/**
* List of updated files
*/
updated: string[];
updated: string[] | FileWithProjectReferenceRedirectInfo[];
/**
* List of files that have had their project reference redirect status updated
* Only provided when the synchronizeProjectList request has includeProjectReferenceRedirectInfo set to true
*/
updatedRedirects?: FileWithProjectReferenceRedirectInfo[];
}
/**
@@ -1357,8 +1374,10 @@ namespace ts.server.protocol {
info?: ProjectVersionInfo;
/**
* List of files in project (might be omitted if current state of project can be computed using only information from 'changes')
* This property will have type FileWithProjectReferenceRedirectInfo[] if includeProjectReferenceRedirectInfo is set to true in
* the corresponding SynchronizeProjectList request; otherwise, it will have type string[].
*/
files?: string[];
files?: string[] | FileWithProjectReferenceRedirectInfo[];
/**
* Set of changes in project (omitted if the entire set of files in project should be replaced)
*/
@@ -1621,6 +1640,11 @@ namespace ts.server.protocol {
* List of last known projects
*/
knownProjects: ProjectVersionInfo[];
/**
* If true, response specifies whether or not each file in each project
* is a source from a project reference redirect
*/
includeProjectReferenceRedirectInfo?: boolean;
}
/**
+1 -1
View File
@@ -2265,7 +2265,7 @@ namespace ts.server {
return this.requiredResponse(/*response*/ true);
},
[CommandNames.SynchronizeProjectList]: (request: protocol.SynchronizeProjectListRequest) => {
const result = this.projectService.synchronizeProjectList(request.arguments.knownProjects);
const result = this.projectService.synchronizeProjectList(request.arguments.knownProjects, request.arguments.includeProjectReferenceRedirectInfo);
if (!result.some(p => p.projectErrors && p.projectErrors.length !== 0)) {
return this.requiredResponse(result);
}
@@ -1431,6 +1431,176 @@ var x = 10;`
host.checkTimeoutQueueLength(0);
});
it("synchronizeProjectList provides redirect info when requested", () => {
const projectRootPath = "/users/username/projects/project";
const fileA: File = {
path: `${projectRootPath}/A/a.ts`,
content: "export const foo: string = 5;"
};
const configA: File = {
path: `${projectRootPath}/A/tsconfig.json`,
content: `{
"compilerOptions": {
"composite": true,
"declaration": true
}
}`
};
const fileB: File = {
path: `${projectRootPath}/B/b.ts`,
content: "import { foo } from \"../A/a\"; console.log(foo);"
};
const configB: File = {
path: `${projectRootPath}/B/tsconfig.json`,
content: `{
"compilerOptions": {
"composite": true,
"declaration": true
},
"references": [
{ "path": "../A" }
]
}`
};
const files = [fileA, fileB, configA, configB, libFile];
const host = createServerHost(files);
const projectService = createProjectService(host);
projectService.openClientFile(fileA.path);
projectService.openClientFile(fileB.path);
const knownProjects = projectService.synchronizeProjectList([], /*includeProjectReferenceRedirectInfo*/ true);
assert.deepEqual(knownProjects[0].files, [
{
fileName: libFile.path,
isSourceOfProjectReferenceRedirect: false
},
{
fileName: fileA.path,
isSourceOfProjectReferenceRedirect: false
},
{
fileName: configA.path,
isSourceOfProjectReferenceRedirect: false
}
]);
assert.deepEqual(knownProjects[1].files, [
{
fileName: libFile.path,
isSourceOfProjectReferenceRedirect: false
},
{
fileName: fileA.path,
isSourceOfProjectReferenceRedirect: true,
},
{
fileName: fileB.path,
isSourceOfProjectReferenceRedirect: false,
},
{
fileName: configB.path,
isSourceOfProjectReferenceRedirect: false
}
]);
});
it("synchronizeProjectList provides updates to redirect info when requested", () => {
const projectRootPath = "/users/username/projects/project";
const fileA: File = {
path: `${projectRootPath}/A/a.ts`,
content: "export const foo: string = 5;"
};
const configA: File = {
path: `${projectRootPath}/A/tsconfig.json`,
content: `{
"compilerOptions": {
"composite": true,
"declaration": true
}
}`
};
const fileB: File = {
path: `${projectRootPath}/B/b.ts`,
content: "import { foo } from \"../B/b2\"; console.log(foo);"
};
const fileB2: File = {
path: `${projectRootPath}/B/b2.ts`,
content: "export const foo: string = 5;"
};
const configB: File = {
path: `${projectRootPath}/B/tsconfig.json`,
content: `{
"compilerOptions": {
"composite": true,
"declaration": true
},
"references": [
{ "path": "../A" }
]
}`
};
const files = [fileA, fileB, fileB2, configA, configB, libFile];
const host = createServerHost(files);
const projectService = createProjectService(host);
projectService.openClientFile(fileA.path);
projectService.openClientFile(fileB.path);
const knownProjects = projectService.synchronizeProjectList([], /*includeProjectReferenceRedirectInfo*/ true);
assert.deepEqual(knownProjects[0].files, [
{
fileName: libFile.path,
isSourceOfProjectReferenceRedirect: false
},
{
fileName: fileA.path,
isSourceOfProjectReferenceRedirect: false
},
{
fileName: configA.path,
isSourceOfProjectReferenceRedirect: false
}
]);
assert.deepEqual(knownProjects[1].files, [
{
fileName: libFile.path,
isSourceOfProjectReferenceRedirect: false
},
{
fileName: fileB2.path,
isSourceOfProjectReferenceRedirect: false,
},
{
fileName: fileB.path,
isSourceOfProjectReferenceRedirect: false,
},
{
fileName: configB.path,
isSourceOfProjectReferenceRedirect: false
}
]);
host.modifyFile(configA.path, `{
"compilerOptions": {
"composite": true,
"declaration": true
},
"include": [
"**/*",
"../B/b2.ts"
]
}`);
const newKnownProjects = projectService.synchronizeProjectList(knownProjects.map(proj => proj.info!), /*includeProjectReferenceRedirectInfo*/ true);
assert.deepEqual(newKnownProjects[0].changes?.added, [
{
fileName: fileB2.path,
isSourceOfProjectReferenceRedirect: false
}
]);
assert.deepEqual(newKnownProjects[1].changes?.updatedRedirects, [
{
fileName: fileB2.path,
isSourceOfProjectReferenceRedirect: true
}
]);
});
it("handles delayed directory watch invoke on file creation", () => {
const projectRootPath = "/users/username/projects/project";
const fileB: File = {
+18 -3
View File
@@ -7066,6 +7066,16 @@ declare namespace ts.server.protocol {
* compiler settings.
*/
type ExternalProjectCompilerOptions = CompilerOptions & CompileOnSaveMixin & WatchOptions;
interface FileWithProjectReferenceRedirectInfo {
/**
* Name of file
*/
fileName: string;
/**
* True if the file is primarily included in a referenced project
*/
isSourceOfProjectReferenceRedirect: boolean;
}
/**
* Represents a set of changes that happen in project
*/
@@ -7073,15 +7083,20 @@ declare namespace ts.server.protocol {
/**
* List of added files
*/
added: string[];
added: string[] | FileWithProjectReferenceRedirectInfo[];
/**
* List of removed files
*/
removed: string[];
removed: string[] | FileWithProjectReferenceRedirectInfo[];
/**
* List of updated files
*/
updated: string[];
updated: string[] | FileWithProjectReferenceRedirectInfo[];
/**
* List of files that have had their project reference redirect status updated
* Only provided when the synchronizeProjectList request has includeProjectReferenceRedirectInfo set to true
*/
updatedRedirects?: FileWithProjectReferenceRedirectInfo[];
}
/**
* Information found in a configure request.