-
Notifications
You must be signed in to change notification settings - Fork 12.9k
Enable tsserver global operations to be performed on all projects #7353
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
55f4b02
7663a96
2835e25
8294a18
fb0d720
db6f5bd
19596de
8709975
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -412,14 +412,15 @@ namespace ts.server { | |
|
||
private getRenameLocations(line: number, offset: number, fileName: string, findInComments: boolean, findInStrings: boolean): protocol.RenameResponseBody { | ||
const file = ts.normalizePath(fileName); | ||
const project = this.projectService.getProjectForFile(file); | ||
if (!project) { | ||
const defaultProject = this.projectService.getProjectForFile(file); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why not ? const projects = this.projectService.findReferencingProjects(info);
if (!projects.lenght) {
throw Errors.NoProject;
}
const defaultProject = projects[0]; |
||
if (!defaultProject) { | ||
throw Errors.NoProject; | ||
} | ||
|
||
const compilerService = project.compilerService; | ||
const position = compilerService.host.lineOffsetToPosition(file, line, offset); | ||
const renameInfo = compilerService.languageService.getRenameInfo(file, position); | ||
// The rename info should be the same for every project | ||
const defaultProjectCompilerService = defaultProject.compilerService; | ||
const position = defaultProjectCompilerService.host.lineOffsetToPosition(file, line, offset); | ||
const renameInfo = defaultProjectCompilerService.languageService.getRenameInfo(file, position); | ||
if (!renameInfo) { | ||
return undefined; | ||
} | ||
|
@@ -431,98 +432,129 @@ namespace ts.server { | |
}; | ||
} | ||
|
||
const renameLocations = compilerService.languageService.findRenameLocations(file, position, findInStrings, findInComments); | ||
if (!renameLocations) { | ||
return undefined; | ||
} | ||
|
||
const bakedRenameLocs = renameLocations.map(location => (<protocol.FileSpan>{ | ||
file: location.fileName, | ||
start: compilerService.host.positionToLineOffset(location.fileName, location.textSpan.start), | ||
end: compilerService.host.positionToLineOffset(location.fileName, ts.textSpanEnd(location.textSpan)), | ||
})).sort((a, b) => { | ||
if (a.file < b.file) { | ||
return -1; | ||
} | ||
else if (a.file > b.file) { | ||
return 1; | ||
const locs: protocol.SpanGroup[] = []; | ||
const info = this.projectService.getScriptInfo(file); | ||
const projects = this.projectService.findReferencingProjects(info); | ||
for (const project of projects) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. consider adding a helper function, function forEachProject<T>(projects: Project[], action: (project: Project) => T[], compare: (a: T, b: T) => number, areEqual: (a: T, b: T) => boolean): T[] {
var result: T[] = [];
for (var project of projects) {
result = concatenate(result, action(project));
}
result = result.sort(compare);
return projects.length === 1 ? result : ts.deduplicate(result, areEqual);
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. or even better: function forEachProject<T>(projects: Project[], action: (project: Project) => T[], compare: (a: T, b: T) => number, areEqual: (a: T, b: T) => boolean): T[] {
var result = projects.reduce<T[]>((previous, current) => concatenate(previous, action(current)), []).sort(compare);
return projects.length > 1 ? deduplicate(result, areEqual): result;
} |
||
const compilerService = project.compilerService; | ||
const renameLocations = compilerService.languageService.findRenameLocations(file, position, findInStrings, findInComments); | ||
if (!renameLocations) { | ||
continue; | ||
} | ||
else { | ||
// reverse sort assuming no overlap | ||
if (a.start.line < b.start.line) { | ||
return 1; | ||
} | ||
else if (a.start.line > b.start.line) { | ||
|
||
const bakedRenameLocs = renameLocations.map(location => (<protocol.FileSpan>{ | ||
file: location.fileName, | ||
start: compilerService.host.positionToLineOffset(location.fileName, location.textSpan.start), | ||
end: compilerService.host.positionToLineOffset(location.fileName, ts.textSpanEnd(location.textSpan)), | ||
})).sort((a, b) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. consider extracting this to a helper compareRenameLocation |
||
if (a.file < b.file) { | ||
return -1; | ||
} | ||
else if (a.file > b.file) { | ||
return 1; | ||
} | ||
else { | ||
return b.start.offset - a.start.offset; | ||
// reverse sort assuming no overlap | ||
if (a.start.line < b.start.line) { | ||
return 1; | ||
} | ||
else if (a.start.line > b.start.line) { | ||
return -1; | ||
} | ||
else { | ||
return b.start.offset - a.start.offset; | ||
} | ||
} | ||
} | ||
}).reduce<protocol.SpanGroup[]>((accum: protocol.SpanGroup[], cur: protocol.FileSpan) => { | ||
let curFileAccum: protocol.SpanGroup; | ||
if (accum.length > 0) { | ||
curFileAccum = accum[accum.length - 1]; | ||
if (curFileAccum.file != cur.file) { | ||
curFileAccum = undefined; | ||
}).reduce<protocol.SpanGroup[]>((accum: protocol.SpanGroup[], cur: protocol.FileSpan) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You shouldn't need the type argument There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. or the type annotations actually |
||
let curFileAccum: protocol.SpanGroup; | ||
if (accum.length > 0) { | ||
curFileAccum = accum[accum.length - 1]; | ||
if (curFileAccum.file != cur.file) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. !== |
||
curFileAccum = undefined; | ||
} | ||
} | ||
} | ||
if (!curFileAccum) { | ||
curFileAccum = { file: cur.file, locs: [] }; | ||
accum.push(curFileAccum); | ||
} | ||
curFileAccum.locs.push({ start: cur.start, end: cur.end }); | ||
return accum; | ||
}, []); | ||
if (!curFileAccum) { | ||
curFileAccum = { file: cur.file, locs: [] }; | ||
accum.push(curFileAccum); | ||
} | ||
curFileAccum.locs.push({ start: cur.start, end: cur.end }); | ||
return accum; | ||
}, []); | ||
|
||
addRange(locs, bakedRenameLocs); | ||
} | ||
|
||
return { info: renameInfo, locs: bakedRenameLocs }; | ||
return { info: renameInfo, locs: deduplicate(locs, areSpanGroupsForTheSameFile) }; | ||
|
||
function areSpanGroupsForTheSameFile(a: protocol.SpanGroup, b: protocol.SpanGroup) { | ||
if (a && b) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
return a.file === b.file; | ||
} | ||
return false; | ||
} | ||
} | ||
|
||
private getReferences(line: number, offset: number, fileName: string): protocol.ReferencesResponseBody { | ||
// TODO: get all projects for this file; report refs for all projects deleting duplicates | ||
// can avoid duplicates by eliminating same ref file from subsequent projects | ||
const file = ts.normalizePath(fileName); | ||
const project = this.projectService.getProjectForFile(file); | ||
if (!project) { | ||
const defaultProject = this.projectService.getProjectForFile(file); | ||
if (!defaultProject) { | ||
throw Errors.NoProject; | ||
} | ||
|
||
const compilerService = project.compilerService; | ||
const position = compilerService.host.lineOffsetToPosition(file, line, offset); | ||
|
||
const references = compilerService.languageService.getReferencesAtPosition(file, position); | ||
if (!references) { | ||
return undefined; | ||
} | ||
|
||
const nameInfo = compilerService.languageService.getQuickInfoAtPosition(file, position); | ||
const position = defaultProject.compilerService.host.lineOffsetToPosition(file, line, offset); | ||
const nameInfo = defaultProject.compilerService.languageService.getQuickInfoAtPosition(file, position); | ||
if (!nameInfo) { | ||
return undefined; | ||
} | ||
|
||
const displayString = ts.displayPartsToString(nameInfo.displayParts); | ||
const nameSpan = nameInfo.textSpan; | ||
const nameColStart = compilerService.host.positionToLineOffset(file, nameSpan.start).offset; | ||
const nameText = compilerService.host.getScriptSnapshot(file).getText(nameSpan.start, ts.textSpanEnd(nameSpan)); | ||
const bakedRefs: protocol.ReferencesResponseItem[] = references.map(ref => { | ||
const start = compilerService.host.positionToLineOffset(ref.fileName, ref.textSpan.start); | ||
const refLineSpan = compilerService.host.lineToTextSpan(ref.fileName, start.line - 1); | ||
const snap = compilerService.host.getScriptSnapshot(ref.fileName); | ||
const lineText = snap.getText(refLineSpan.start, ts.textSpanEnd(refLineSpan)).replace(/\r|\n/g, ""); | ||
return { | ||
file: ref.fileName, | ||
start: start, | ||
lineText: lineText, | ||
end: compilerService.host.positionToLineOffset(ref.fileName, ts.textSpanEnd(ref.textSpan)), | ||
isWriteAccess: ref.isWriteAccess | ||
}; | ||
}).sort(compareFileStart); | ||
const nameColStart = defaultProject.compilerService.host.positionToLineOffset(file, nameSpan.start).offset; | ||
const nameText = defaultProject.compilerService.host.getScriptSnapshot(file).getText(nameSpan.start, ts.textSpanEnd(nameSpan)); | ||
|
||
const info = this.projectService.getScriptInfo(file); | ||
const projects = this.projectService.findReferencingProjects(info); | ||
const refs: protocol.ReferencesResponseItem[] = []; | ||
for (const project of projects) | ||
{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Previous line |
||
const compilerService = project.compilerService; | ||
const references = compilerService.languageService.getReferencesAtPosition(file, position); | ||
if (!references) { | ||
continue; | ||
} | ||
|
||
const bakedRefs: protocol.ReferencesResponseItem[] = references.map(ref => { | ||
const start = compilerService.host.positionToLineOffset(ref.fileName, ref.textSpan.start); | ||
const refLineSpan = compilerService.host.lineToTextSpan(ref.fileName, start.line - 1); | ||
const snap = compilerService.host.getScriptSnapshot(ref.fileName); | ||
const lineText = snap.getText(refLineSpan.start, ts.textSpanEnd(refLineSpan)).replace(/\r|\n/g, ""); | ||
return { | ||
file: ref.fileName, | ||
start: start, | ||
lineText: lineText, | ||
end: compilerService.host.positionToLineOffset(ref.fileName, ts.textSpanEnd(ref.textSpan)), | ||
isWriteAccess: ref.isWriteAccess | ||
}; | ||
}).sort(compareFileStart); | ||
|
||
addRange(refs, bakedRefs); | ||
} | ||
|
||
return { | ||
refs: bakedRefs, | ||
refs: deduplicate(refs, areReferencesResponseItemsForTheSameLocation), | ||
symbolName: nameText, | ||
symbolStartOffset: nameColStart, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why deduplicate again? |
||
symbolDisplayString: displayString | ||
}; | ||
|
||
function areReferencesResponseItemsForTheSameLocation(a: protocol.ReferencesResponseItem, b: protocol.ReferencesResponseItem) { | ||
if (a && b) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When will one of these be undefined? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Originally it might be undefined when |
||
return a.file === b.file && | ||
a.start === b.start && | ||
a.end === b.end; | ||
} | ||
return false; | ||
} | ||
} | ||
|
||
/** | ||
|
@@ -836,41 +868,57 @@ namespace ts.server { | |
|
||
private getNavigateToItems(searchValue: string, fileName: string, maxResultCount?: number): protocol.NavtoItem[] { | ||
const file = ts.normalizePath(fileName); | ||
const project = this.projectService.getProjectForFile(file); | ||
if (!project) { | ||
const defaultProject = this.projectService.getProjectForFile(file); | ||
if (!defaultProject) { | ||
throw Errors.NoProject; | ||
} | ||
|
||
const compilerService = project.compilerService; | ||
const navItems = compilerService.languageService.getNavigateToItems(searchValue, maxResultCount); | ||
if (!navItems) { | ||
return undefined; | ||
const info = this.projectService.getScriptInfo(file); | ||
const projects = this.projectService.findReferencingProjects(info); | ||
const allNavToItems: protocol.NavtoItem[] = []; | ||
for (const project of projects) { | ||
const compilerService = project.compilerService; | ||
const navItems = compilerService.languageService.getNavigateToItems(searchValue, maxResultCount); | ||
if (!navItems) { | ||
continue; | ||
} | ||
|
||
const bakedNavItems = navItems.map((navItem) => { | ||
const start = compilerService.host.positionToLineOffset(navItem.fileName, navItem.textSpan.start); | ||
const end = compilerService.host.positionToLineOffset(navItem.fileName, ts.textSpanEnd(navItem.textSpan)); | ||
const bakedItem: protocol.NavtoItem = { | ||
name: navItem.name, | ||
kind: navItem.kind, | ||
file: navItem.fileName, | ||
start: start, | ||
end: end, | ||
}; | ||
if (navItem.kindModifiers && (navItem.kindModifiers != "")) { | ||
bakedItem.kindModifiers = navItem.kindModifiers; | ||
} | ||
if (navItem.matchKind !== "none") { | ||
bakedItem.matchKind = navItem.matchKind; | ||
} | ||
if (navItem.containerName && (navItem.containerName.length > 0)) { | ||
bakedItem.containerName = navItem.containerName; | ||
} | ||
if (navItem.containerKind && (navItem.containerKind.length > 0)) { | ||
bakedItem.containerKind = navItem.containerKind; | ||
} | ||
return bakedItem; | ||
}); | ||
addRange(allNavToItems, bakedNavItems); | ||
} | ||
return deduplicate(allNavToItems, areNavToItemsForTheSameLocation); | ||
|
||
return navItems.map((navItem) => { | ||
const start = compilerService.host.positionToLineOffset(navItem.fileName, navItem.textSpan.start); | ||
const end = compilerService.host.positionToLineOffset(navItem.fileName, ts.textSpanEnd(navItem.textSpan)); | ||
const bakedItem: protocol.NavtoItem = { | ||
name: navItem.name, | ||
kind: navItem.kind, | ||
file: navItem.fileName, | ||
start: start, | ||
end: end, | ||
}; | ||
if (navItem.kindModifiers && (navItem.kindModifiers != "")) { | ||
bakedItem.kindModifiers = navItem.kindModifiers; | ||
} | ||
if (navItem.matchKind !== "none") { | ||
bakedItem.matchKind = navItem.matchKind; | ||
} | ||
if (navItem.containerName && (navItem.containerName.length > 0)) { | ||
bakedItem.containerName = navItem.containerName; | ||
function areNavToItemsForTheSameLocation(a: protocol.NavtoItem, b: protocol.NavtoItem) { | ||
if (a && b) { | ||
return a.file === b.file && | ||
a.start === b.start && | ||
a.end === b.end; | ||
} | ||
if (navItem.containerKind && (navItem.containerKind.length > 0)) { | ||
bakedItem.containerKind = navItem.containerKind; | ||
} | ||
return bakedItem; | ||
}); | ||
return false; | ||
} | ||
} | ||
|
||
private getBraceMatching(line: number, offset: number, fileName: string): protocol.TextSpan[] { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
/// <reference path="fourslash.ts" /> | ||
|
||
//@Filename: a.ts | ||
////var /*1*/x: number; | ||
|
||
//@Filename: b.ts | ||
/////// <reference path="a.ts" /> | ||
////x++; | ||
|
||
//@Filename: c.ts | ||
/////// <reference path="a.ts" /> | ||
////x++; | ||
|
||
goTo.file("a.ts"); | ||
goTo.marker("1"); | ||
|
||
verify.referencesCountIs(3); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
/// <reference path="fourslash.ts" /> | ||
|
||
//@Filename: a.ts | ||
////var x: number; | ||
|
||
//@Filename: b.ts | ||
////var x: number; | ||
|
||
//@Filename: c.ts | ||
/////// <reference path="a.ts" /> | ||
/////// <reference path="b.ts" /> | ||
/////**/x++; | ||
|
||
goTo.file("c.ts"); | ||
goTo.marker(); | ||
|
||
verify.definitionCountIs(2); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
/// <reference path="fourslash.ts" /> | ||
|
||
//@Filename: a.ts | ||
////var /*1*/[|x|]: number; | ||
|
||
//@Filename: b.ts | ||
/////// <reference path="a.ts" /> | ||
////[|x|]++; | ||
|
||
//@Filename: c.ts | ||
/////// <reference path="a.ts" /> | ||
////[|x|]++; | ||
|
||
goTo.file("a.ts"); | ||
goTo.marker("1"); | ||
|
||
verify.renameLocations( /*findInStrings*/ false, /*findInComments*/ false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.