Merge branch 'master' into incrementalBuildInfo

This commit is contained in:
Sheetal Nandi
2019-03-08 11:42:19 -08:00
117 changed files with 4728 additions and 5649 deletions

View File

@@ -408,6 +408,21 @@ namespace ts.server {
}
}
/*@internal*/
export interface OpenFileArguments {
fileName: string;
content?: string;
scriptKind?: protocol.ScriptKindName | ScriptKind;
hasMixedContent?: boolean;
projectRootPath?: string;
}
/*@internal*/
export interface ChangeFileArguments {
fileName: string;
changes: Iterator<TextChange>;
}
export class ProjectService {
/*@internal*/
@@ -988,7 +1003,7 @@ namespace ts.server {
fileOrDirectory => {
const fileOrDirectoryPath = this.toPath(fileOrDirectory);
project.getCachedDirectoryStructureHost().addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath);
if (isPathInNodeModulesStartingWithDot(fileOrDirectoryPath)) return;
if (isPathIgnored(fileOrDirectoryPath)) return;
const configFilename = project.getConfigFilePath();
// If the the added or created file or directory is not supported file name, ignore the file
@@ -1129,11 +1144,22 @@ namespace ts.server {
return project;
}
private assignOrphanScriptInfosToInferredProject() {
// collect orphaned files and assign them to inferred project just like we treat open of a file
this.openFiles.forEach((projectRootPath, path) => {
const info = this.getScriptInfoForPath(path as Path)!;
// collect all orphaned script infos from open files
if (info.isOrphan()) {
this.assignOrphanScriptInfoToInferredProject(info, projectRootPath);
}
});
}
/**
* Remove this file from the set of open, non-configured files.
* @param info The file that has been closed or newly configured
*/
private closeOpenFile(info: ScriptInfo): void {
private closeOpenFile(info: ScriptInfo, skipAssignOrphanScriptInfosToInferredProject?: true) {
// Closing file should trigger re-reading the file content from disk. This is
// because the user may chose to discard the buffer content before saving
// to the disk, and the server's version of the file can be out of sync.
@@ -1177,15 +1203,8 @@ namespace ts.server {
this.openFiles.delete(info.path);
if (ensureProjectsForOpenFiles) {
// collect orphaned files and assign them to inferred project just like we treat open of a file
this.openFiles.forEach((projectRootPath, path) => {
const info = this.getScriptInfoForPath(path as Path)!;
// collect all orphaned script infos from open files
if (info.isOrphan()) {
this.assignOrphanScriptInfoToInferredProject(info, projectRootPath);
}
});
if (!skipAssignOrphanScriptInfosToInferredProject && ensureProjectsForOpenFiles) {
this.assignOrphanScriptInfosToInferredProject();
}
// Cleanup script infos that arent part of any project (eg. those could be closed script infos not referenced by any project)
@@ -1200,6 +1219,8 @@ namespace ts.server {
else {
this.handleDeletedFile(info);
}
return ensureProjectsForOpenFiles;
}
private deleteScriptInfo(info: ScriptInfo) {
@@ -2051,7 +2072,7 @@ namespace ts.server {
watchDir,
(fileOrDirectory) => {
const fileOrDirectoryPath = this.toPath(fileOrDirectory);
if (isPathInNodeModulesStartingWithDot(fileOrDirectoryPath)) return;
if (isPathIgnored(fileOrDirectoryPath)) return;
// Has extension
Debug.assert(result.refCount > 0);
@@ -2571,20 +2592,22 @@ namespace ts.server {
});
}
openClientFileWithNormalizedPath(fileName: NormalizedPath, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, projectRootPath?: NormalizedPath): OpenConfiguredProjectResult {
private getOrCreateOpenScriptInfo(fileName: NormalizedPath, fileContent: string | undefined, scriptKind: ScriptKind | undefined, hasMixedContent: boolean | undefined, projectRootPath: NormalizedPath | undefined) {
const info = this.getOrCreateScriptInfoOpenedByClientForNormalizedPath(fileName, projectRootPath ? this.getNormalizedAbsolutePath(projectRootPath) : this.currentDirectory, fileContent, scriptKind, hasMixedContent)!; // TODO: GH#18217
this.openFiles.set(info.path, projectRootPath);
return info;
}
private assignProjectToOpenedScriptInfo(info: ScriptInfo): OpenConfiguredProjectResult {
let configFileName: NormalizedPath | undefined;
let configFileErrors: ReadonlyArray<Diagnostic> | undefined;
const info = this.getOrCreateScriptInfoOpenedByClientForNormalizedPath(fileName, projectRootPath ? this.getNormalizedAbsolutePath(projectRootPath) : this.currentDirectory, fileContent, scriptKind, hasMixedContent)!; // TODO: GH#18217
this.openFiles.set(info.path, projectRootPath);
let project: ConfiguredProject | ExternalProject | undefined = this.findExternalProjectContainingOpenScriptInfo(info);
if (!project && !this.syntaxOnly) { // Checking syntaxOnly is an optimization
configFileName = this.getConfigFileNameForFile(info);
if (configFileName) {
project = this.findConfiguredProjectByProjectName(configFileName);
if (!project) {
project = this.createLoadAndUpdateConfiguredProject(configFileName, `Creating possible configured project for ${fileName} to open`);
project = this.createLoadAndUpdateConfiguredProject(configFileName, `Creating possible configured project for ${info.fileName} to open`);
// Send the event only if the project got created as part of this open request and info is part of the project
if (info.isOrphan()) {
// Since the file isnt part of configured project, do not send config file info
@@ -2592,7 +2615,7 @@ namespace ts.server {
}
else {
configFileErrors = project.getAllProjectErrors();
this.sendConfigFileDiagEvent(project, fileName);
this.sendConfigFileDiagEvent(project, info.fileName);
}
}
else {
@@ -2614,10 +2637,14 @@ namespace ts.server {
// At this point if file is part of any any configured or external project, then it would be present in the containing projects
// So if it still doesnt have any containing projects, it needs to be part of inferred project
if (info.isOrphan()) {
this.assignOrphanScriptInfoToInferredProject(info, projectRootPath);
Debug.assert(this.openFiles.has(info.path));
this.assignOrphanScriptInfoToInferredProject(info, this.openFiles.get(info.path));
}
Debug.assert(!info.isOrphan());
return { configFileName, configFileErrors };
}
private cleanupAfterOpeningFile() {
// This was postponed from closeOpenFile to after opening next file,
// so that we can reuse the project if we need to right away
this.removeOrphanConfiguredProjects();
@@ -2637,9 +2664,14 @@ namespace ts.server {
this.removeOrphanScriptInfos();
this.printProjects();
}
openClientFileWithNormalizedPath(fileName: NormalizedPath, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean, projectRootPath?: NormalizedPath): OpenConfiguredProjectResult {
const info = this.getOrCreateOpenScriptInfo(fileName, fileContent, scriptKind, hasMixedContent, projectRootPath);
const result = this.assignProjectToOpenedScriptInfo(info);
this.cleanupAfterOpeningFile();
this.telemetryOnOpenFile(info);
return { configFileName, configFileErrors };
return result;
}
private removeOrphanConfiguredProjects() {
@@ -2746,12 +2778,16 @@ namespace ts.server {
* Close file whose contents is managed by the client
* @param filename is absolute pathname
*/
closeClientFile(uncheckedFileName: string) {
closeClientFile(uncheckedFileName: string): void;
/*@internal*/
closeClientFile(uncheckedFileName: string, skipAssignOrphanScriptInfosToInferredProject: true): boolean;
closeClientFile(uncheckedFileName: string, skipAssignOrphanScriptInfosToInferredProject?: true) {
const info = this.getScriptInfoForNormalizedPath(toNormalizedPath(uncheckedFileName));
if (info) {
this.closeOpenFile(info);
const result = info ? this.closeOpenFile(info, skipAssignOrphanScriptInfosToInferredProject) : false;
if (!skipAssignOrphanScriptInfosToInferredProject) {
this.printProjects();
}
this.printProjects();
return result;
}
private collectChanges(lastKnownProjectVersions: protocol.ProjectVersionInfo[], currentProjects: Project[], result: ProjectFilesWithTSDiagnostics[]): void {
@@ -2771,36 +2807,68 @@ namespace ts.server {
}
/* @internal */
applyChangesInOpenFiles(openFiles: protocol.ExternalFile[] | undefined, changedFiles: protocol.ChangedOpenFile[] | undefined, closedFiles: string[] | undefined): void {
applyChangesInOpenFiles(openFiles: Iterator<OpenFileArguments> | undefined, changedFiles?: Iterator<ChangeFileArguments>, closedFiles?: string[]): void {
let openScriptInfos: ScriptInfo[] | undefined;
let assignOrphanScriptInfosToInferredProject = false;
if (openFiles) {
for (const file of openFiles) {
while (true) {
const { value: file, done } = openFiles.next();
if (done) break;
const scriptInfo = this.getScriptInfo(file.fileName);
Debug.assert(!scriptInfo || !scriptInfo.isScriptOpen(), "Script should not exist and not be open already");
const normalizedPath = scriptInfo ? scriptInfo.fileName : toNormalizedPath(file.fileName);
this.openClientFileWithNormalizedPath(normalizedPath, file.content, tryConvertScriptKindName(file.scriptKind!), file.hasMixedContent); // TODO: GH#18217
// Create script infos so we have the new content for all the open files before we do any updates to projects
const info = this.getOrCreateOpenScriptInfo(
scriptInfo ? scriptInfo.fileName : toNormalizedPath(file.fileName),
file.content,
tryConvertScriptKindName(file.scriptKind!),
file.hasMixedContent,
file.projectRootPath ? toNormalizedPath(file.projectRootPath) : undefined
);
(openScriptInfos || (openScriptInfos = [])).push(info);
}
}
if (changedFiles) {
for (const file of changedFiles) {
while (true) {
const { value: file, done } = changedFiles.next();
if (done) break;
const scriptInfo = this.getScriptInfo(file.fileName)!;
Debug.assert(!!scriptInfo);
// Make edits to script infos and marks containing project as dirty
this.applyChangesToFile(scriptInfo, file.changes);
}
}
if (closedFiles) {
for (const file of closedFiles) {
this.closeClientFile(file);
// Close files, but dont assign projects to orphan open script infos, that part comes later
assignOrphanScriptInfosToInferredProject = this.closeClientFile(file, /*skipAssignOrphanScriptInfosToInferredProject*/ true) || assignOrphanScriptInfosToInferredProject;
}
}
// All the script infos now exist, so ok to go update projects for open files
if (openScriptInfos) {
openScriptInfos.forEach(info => this.assignProjectToOpenedScriptInfo(info));
}
// While closing files there could be open files that needed assigning new inferred projects, do it now
if (assignOrphanScriptInfosToInferredProject) {
this.assignOrphanScriptInfosToInferredProject();
}
// Cleanup projects
this.cleanupAfterOpeningFile();
// Telemetry
forEach(openScriptInfos, info => this.telemetryOnOpenFile(info));
this.printProjects();
}
/* @internal */
applyChangesToFile(scriptInfo: ScriptInfo, changes: TextChange[]) {
// apply changes in reverse order
for (let i = changes.length - 1; i >= 0; i--) {
const change = changes[i];
applyChangesToFile(scriptInfo: ScriptInfo, changes: Iterator<TextChange>) {
while (true) {
const { value: change, done } = changes.next();
if (done) break;
scriptInfo.editContent(change.span.start, change.span.start + change.span.length, change.newText);
}
}

View File

@@ -92,6 +92,7 @@ namespace ts.server.protocol {
SynchronizeProjectList = "synchronizeProjectList",
/* @internal */
ApplyChangedToOpenFiles = "applyChangedToOpenFiles",
UpdateOpen = "updateOpen",
/* @internal */
EncodedSemanticClassificationsFull = "encodedSemanticClassifications-full",
/* @internal */
@@ -1543,6 +1544,32 @@ namespace ts.server.protocol {
closedFiles?: string[];
}
/**
* Request to synchronize list of open files with the client
*/
export interface UpdateOpenRequest extends Request {
command: CommandTypes.UpdateOpen;
arguments: UpdateOpenRequestArgs;
}
/**
* Arguments to UpdateOpenRequest
*/
export interface UpdateOpenRequestArgs {
/**
* List of newly open files
*/
openFiles?: OpenRequestArgs[];
/**
* List of open files files that were changes
*/
changedFiles?: FileCodeEdits[];
/**
* List of files that were closed
*/
closedFiles?: string[];
}
/**
* Request to set compiler options for inferred projects.
* External projects are opened / closed explicitly.

View File

@@ -1654,10 +1654,10 @@ namespace ts.server {
const end = scriptInfo.lineOffsetToPosition(args.endLine, args.endOffset);
if (start >= 0) {
this.changeSeq++;
this.projectService.applyChangesToFile(scriptInfo, [{
this.projectService.applyChangesToFile(scriptInfo, singleIterator({
span: { start, length: end - start },
newText: args.insertString! // TODO: GH#18217
}]);
}));
}
}
@@ -2096,9 +2096,39 @@ namespace ts.server {
});
return this.requiredResponse(converted);
},
[CommandNames.UpdateOpen]: (request: protocol.UpdateOpenRequest) => {
this.changeSeq++;
this.projectService.applyChangesInOpenFiles(
request.arguments.openFiles && mapIterator(arrayIterator(request.arguments.openFiles), file => ({
fileName: file.file,
content: file.fileContent,
scriptKind: file.scriptKindName,
projectRootPath: file.projectRootPath
})),
request.arguments.changedFiles && mapIterator(arrayIterator(request.arguments.changedFiles), file => ({
fileName: file.fileName,
changes: mapDefinedIterator(arrayReverseIterator(file.textChanges), change => {
const scriptInfo = Debug.assertDefined(this.projectService.getScriptInfo(file.fileName));
const start = scriptInfo.lineOffsetToPosition(change.start.line, change.start.offset);
const end = scriptInfo.lineOffsetToPosition(change.end.line, change.end.offset);
return start >= 0 ? { span: { start, length: end - start }, newText: change.newText } : undefined;
})
})),
request.arguments.closedFiles
);
return this.requiredResponse(/*response*/ true);
},
[CommandNames.ApplyChangedToOpenFiles]: (request: protocol.ApplyChangedToOpenFilesRequest) => {
this.changeSeq++;
this.projectService.applyChangesInOpenFiles(request.arguments.openFiles, request.arguments.changedFiles!, request.arguments.closedFiles!); // TODO: GH#18217
this.projectService.applyChangesInOpenFiles(
request.arguments.openFiles && arrayIterator(request.arguments.openFiles),
request.arguments.changedFiles && mapIterator(arrayIterator(request.arguments.changedFiles), file => ({
fileName: file.fileName,
// apply changes in reverse order
changes: arrayReverseIterator(file.changes)
})),
request.arguments.closedFiles
);
// TODO: report errors
return this.requiredResponse(/*response*/ true);
},