Merge branch 'master' into isInMultiLineComment

This commit is contained in:
Arthur Ozga
2017-08-04 14:26:24 -07:00
1154 changed files with 257792 additions and 221227 deletions
+49 -79
View File
@@ -84,13 +84,17 @@ namespace ts.server {
* NOTE: this field is created on demand and should not be accessed directly.
* Use 'getFileInfos' instead.
*/
private fileInfos_doNotAccessDirectly: FileMap<T>;
private fileInfos_doNotAccessDirectly: Map<T>;
constructor(public readonly project: Project, private ctor: { new (scriptInfo: ScriptInfo, project: Project): T }) {
}
private getFileInfos() {
return this.fileInfos_doNotAccessDirectly || (this.fileInfos_doNotAccessDirectly = createFileMap<T>());
return this.fileInfos_doNotAccessDirectly || (this.fileInfos_doNotAccessDirectly = createMap<T>());
}
protected hasFileInfos() {
return !!this.fileInfos_doNotAccessDirectly;
}
public clear() {
@@ -113,7 +117,7 @@ namespace ts.server {
}
protected getFileInfoPaths(): Path[] {
return this.getFileInfos().getKeys();
return arrayFrom(this.getFileInfos().keys() as Iterator<Path>);
}
protected setFileInfo(path: Path, info: T) {
@@ -121,20 +125,22 @@ namespace ts.server {
}
protected removeFileInfo(path: Path) {
this.getFileInfos().remove(path);
this.getFileInfos().delete(path);
}
protected forEachFileInfo(action: (fileInfo: T) => any) {
this.getFileInfos().forEachValue((_path, value) => action(value));
this.getFileInfos().forEach(action);
}
abstract getFilesAffectedBy(scriptInfo: ScriptInfo): string[];
abstract onProjectUpdateGraph(): void;
protected abstract ensureFileInfoIfInProject(scriptInfo: ScriptInfo): void;
/**
* @returns {boolean} whether the emit was conducted or not
*/
emitFile(scriptInfo: ScriptInfo, writeFile: (path: string, data: string, writeByteOrderMark?: boolean) => void): boolean {
this.ensureFileInfoIfInProject(scriptInfo);
const fileInfo = this.getFileInfo(scriptInfo.path);
if (!fileInfo) {
return false;
@@ -158,7 +164,21 @@ namespace ts.server {
super(project, BuilderFileInfo);
}
protected ensureFileInfoIfInProject(scriptInfo: ScriptInfo) {
if (this.project.containsScriptInfo(scriptInfo)) {
this.getOrCreateFileInfo(scriptInfo.path);
}
}
onProjectUpdateGraph() {
if (this.hasFileInfos()) {
this.forEachFileInfo(fileInfo => {
if (!this.project.containsScriptInfo(fileInfo.scriptInfo)) {
// This file was deleted from this project
this.removeFileInfo(fileInfo.scriptInfo.path);
}
});
}
}
/**
@@ -183,57 +203,27 @@ namespace ts.server {
}
class ModuleBuilderFileInfo extends BuilderFileInfo {
references: ModuleBuilderFileInfo[] = [];
referencedBy: ModuleBuilderFileInfo[] = [];
references = createSortedArray<ModuleBuilderFileInfo>();
readonly referencedBy = createSortedArray<ModuleBuilderFileInfo>();
scriptVersionForReferences: string;
static compareFileInfos(lf: ModuleBuilderFileInfo, rf: ModuleBuilderFileInfo): number {
const l = lf.scriptInfo.fileName;
const r = rf.scriptInfo.fileName;
return (l < r ? -1 : (l > r ? 1 : 0));
}
static addToReferenceList(array: ModuleBuilderFileInfo[], fileInfo: ModuleBuilderFileInfo) {
if (array.length === 0) {
array.push(fileInfo);
return;
}
const insertIndex = binarySearch(array, fileInfo, ModuleBuilderFileInfo.compareFileInfos);
if (insertIndex < 0) {
array.splice(~insertIndex, 0, fileInfo);
}
}
static removeFromReferenceList(array: ModuleBuilderFileInfo[], fileInfo: ModuleBuilderFileInfo) {
if (!array || array.length === 0) {
return;
}
if (array[0] === fileInfo) {
array.splice(0, 1);
return;
}
const removeIndex = binarySearch(array, fileInfo, ModuleBuilderFileInfo.compareFileInfos);
if (removeIndex >= 0) {
array.splice(removeIndex, 1);
}
static compareFileInfos(lf: ModuleBuilderFileInfo, rf: ModuleBuilderFileInfo): Comparison {
return compareStrings(lf.scriptInfo.fileName, rf.scriptInfo.fileName);
}
addReferencedBy(fileInfo: ModuleBuilderFileInfo): void {
ModuleBuilderFileInfo.addToReferenceList(this.referencedBy, fileInfo);
insertSorted(this.referencedBy, fileInfo, ModuleBuilderFileInfo.compareFileInfos);
}
removeReferencedBy(fileInfo: ModuleBuilderFileInfo): void {
ModuleBuilderFileInfo.removeFromReferenceList(this.referencedBy, fileInfo);
removeSorted(this.referencedBy, fileInfo, ModuleBuilderFileInfo.compareFileInfos);
}
removeFileReferences() {
for (const reference of this.references) {
reference.removeReferencedBy(this);
}
this.references = [];
clear(this.references);
}
}
@@ -250,20 +240,24 @@ namespace ts.server {
super.clear();
}
private getReferencedFileInfos(fileInfo: ModuleBuilderFileInfo): ModuleBuilderFileInfo[] {
private getReferencedFileInfos(fileInfo: ModuleBuilderFileInfo): SortedArray<ModuleBuilderFileInfo> {
if (!fileInfo.isExternalModuleOrHasOnlyAmbientExternalModules()) {
return [];
return createSortedArray();
}
const referencedFilePaths = this.project.getReferencedFiles(fileInfo.scriptInfo.path);
if (referencedFilePaths.length > 0) {
return map(referencedFilePaths, f => this.getOrCreateFileInfo(f)).sort(ModuleBuilderFileInfo.compareFileInfos);
}
return [];
return toSortedArray(referencedFilePaths.map(f => this.getOrCreateFileInfo(f)), ModuleBuilderFileInfo.compareFileInfos);
}
protected ensureFileInfoIfInProject(_scriptInfo: ScriptInfo) {
this.ensureProjectDependencyGraphUpToDate();
}
onProjectUpdateGraph() {
this.ensureProjectDependencyGraphUpToDate();
// Update the graph only if we have computed graph earlier
if (this.hasFileInfos()) {
this.ensureProjectDependencyGraphUpToDate();
}
}
private ensureProjectDependencyGraphUpToDate() {
@@ -292,39 +286,15 @@ namespace ts.server {
const newReferences = this.getReferencedFileInfos(fileInfo);
const oldReferences = fileInfo.references;
let oldIndex = 0;
let newIndex = 0;
while (oldIndex < oldReferences.length && newIndex < newReferences.length) {
const oldReference = oldReferences[oldIndex];
const newReference = newReferences[newIndex];
const compare = ModuleBuilderFileInfo.compareFileInfos(oldReference, newReference);
if (compare < 0) {
enumerateInsertsAndDeletes(newReferences, oldReferences,
/*inserted*/ newReference => newReference.addReferencedBy(fileInfo),
/*deleted*/ oldReference => {
// New reference is greater then current reference. That means
// the current reference doesn't exist anymore after parsing. So delete
// references.
oldReference.removeReferencedBy(fileInfo);
oldIndex++;
}
else if (compare > 0) {
// A new reference info. Add it.
newReference.addReferencedBy(fileInfo);
newIndex++;
}
else {
// Equal. Go to next
oldIndex++;
newIndex++;
}
}
// Clean old references
for (let i = oldIndex; i < oldReferences.length; i++) {
oldReferences[i].removeReferencedBy(fileInfo);
}
// Update new references
for (let i = newIndex; i < newReferences.length; i++) {
newReferences[i].addReferencedBy(fileInfo);
}
},
/*compare*/ ModuleBuilderFileInfo.compareFileInfos);
fileInfo.references = newReferences;
fileInfo.scriptVersionForReferences = fileInfo.scriptInfo.getLatestVersion();
@@ -386,4 +356,4 @@ namespace ts.server {
return new ModuleBuilder(project);
}
}
}
}
+184 -325
View File
@@ -34,7 +34,7 @@ namespace ts.server {
export class SessionClient implements LanguageService {
private sequence = 0;
private lineMaps: ts.Map<number[]> = ts.createMap<number[]>();
private lineMaps: Map<number[]> = createMap<number[]>();
private messages: string[] = [];
private lastRenameEntry: RenameEntry;
@@ -53,7 +53,7 @@ namespace ts.server {
let lineMap = this.lineMaps.get(fileName);
if (!lineMap) {
const scriptSnapshot = this.host.getScriptSnapshot(fileName);
lineMap = ts.computeLineStarts(scriptSnapshot.getText(0, scriptSnapshot.getLength()));
lineMap = computeLineStarts(scriptSnapshot.getText(0, scriptSnapshot.getLength()));
this.lineMaps.set(fileName, lineMap);
}
return lineMap;
@@ -61,11 +61,11 @@ namespace ts.server {
private lineOffsetToPosition(fileName: string, lineOffset: protocol.Location, lineMap?: number[]): number {
lineMap = lineMap || this.getLineMap(fileName);
return ts.computePositionOfLineAndCharacter(lineMap, lineOffset.line - 1, lineOffset.offset - 1);
return computePositionOfLineAndCharacter(lineMap, lineOffset.line - 1, lineOffset.offset - 1);
}
private positionToOneBasedLineOffset(fileName: string, position: number): protocol.Location {
const lineOffset = ts.computeLineAndCharacterOfPosition(this.getLineMap(fileName), position);
const lineOffset = computeLineAndCharacterOfPosition(this.getLineMap(fileName), position);
return {
line: lineOffset.line + 1,
offset: lineOffset.character + 1
@@ -73,13 +73,7 @@ namespace ts.server {
}
private convertCodeEditsToTextChange(fileName: string, codeEdit: protocol.CodeEdit): ts.TextChange {
const start = this.lineOffsetToPosition(fileName, codeEdit.start);
const end = this.lineOffsetToPosition(fileName, codeEdit.end);
return {
span: ts.createTextSpanFromBounds(start, end),
newText: codeEdit.newText
};
return { span: this.decodeSpan(codeEdit, fileName), newText: codeEdit.newText };
}
private processRequest<T extends protocol.Request>(command: string, args?: any): T {
@@ -130,64 +124,42 @@ namespace ts.server {
return response;
}
openFile(fileName: string, content?: string, scriptKindName?: "TS" | "JS" | "TSX" | "JSX"): void {
const args: protocol.OpenRequestArgs = { file: fileName, fileContent: content, scriptKindName };
openFile(file: string, fileContent?: string, scriptKindName?: "TS" | "JS" | "TSX" | "JSX"): void {
const args: protocol.OpenRequestArgs = { file, fileContent, scriptKindName };
this.processRequest(CommandNames.Open, args);
}
closeFile(fileName: string): void {
const args: protocol.FileRequestArgs = { file: fileName };
closeFile(file: string): void {
const args: protocol.FileRequestArgs = { file };
this.processRequest(CommandNames.Close, args);
}
changeFile(fileName: string, start: number, end: number, newText: string): void {
changeFile(fileName: string, start: number, end: number, insertString: string): void {
// clear the line map after an edit
this.lineMaps.set(fileName, undefined);
const lineOffset = this.positionToOneBasedLineOffset(fileName, start);
const endLineOffset = this.positionToOneBasedLineOffset(fileName, end);
const args: protocol.ChangeRequestArgs = {
file: fileName,
line: lineOffset.line,
offset: lineOffset.offset,
endLine: endLineOffset.line,
endOffset: endLineOffset.offset,
insertString: newText
};
const args: protocol.ChangeRequestArgs = { ...this.createFileLocationRequestArgsWithEndLineAndOffset(fileName, start, end), insertString };
this.processRequest(CommandNames.Change, args);
}
getQuickInfoAtPosition(fileName: string, position: number): QuickInfo {
const lineOffset = this.positionToOneBasedLineOffset(fileName, position);
const args: protocol.FileLocationRequestArgs = {
file: fileName,
line: lineOffset.line,
offset: lineOffset.offset
};
const args = this.createFileLocationRequestArgs(fileName, position);
const request = this.processRequest<protocol.QuickInfoRequest>(CommandNames.Quickinfo, args);
const response = this.processResponse<protocol.QuickInfoResponse>(request);
const start = this.lineOffsetToPosition(fileName, response.body.start);
const end = this.lineOffsetToPosition(fileName, response.body.end);
return {
kind: response.body.kind,
kindModifiers: response.body.kindModifiers,
textSpan: ts.createTextSpanFromBounds(start, end),
textSpan: this.decodeSpan(response.body, fileName),
displayParts: [{ kind: "text", text: response.body.displayString }],
documentation: [{ kind: "text", text: response.body.documentation }],
tags: response.body.tags
};
}
getProjectInfo(fileName: string, needFileNameList: boolean): protocol.ProjectInfo {
const args: protocol.ProjectInfoRequestArgs = {
file: fileName,
needFileNameList: needFileNameList
};
getProjectInfo(file: string, needFileNameList: boolean): protocol.ProjectInfo {
const args: protocol.ProjectInfoRequestArgs = { file, needFileNameList };
const request = this.processRequest<protocol.ProjectInfoRequest>(CommandNames.ProjectInfo, args);
const response = this.processResponse<protocol.ProjectInfoResponse>(request);
@@ -199,13 +171,7 @@ namespace ts.server {
}
getCompletionsAtPosition(fileName: string, position: number): CompletionInfo {
const lineOffset = this.positionToOneBasedLineOffset(fileName, position);
const args: protocol.CompletionsRequestArgs = {
file: fileName,
line: lineOffset.line,
offset: lineOffset.offset,
prefix: undefined
};
const args: protocol.CompletionsRequestArgs = this.createFileLocationRequestArgs(fileName, position);
const request = this.processRequest<protocol.CompletionsRequest>(CommandNames.Completions, args);
const response = this.processResponse<protocol.CompletionsResponse>(request);
@@ -217,11 +183,8 @@ namespace ts.server {
entries: response.body.map(entry => {
if (entry.replacementSpan !== undefined) {
const { name, kind, kindModifiers, sortText, replacementSpan} = entry;
const convertedSpan = createTextSpanFromBounds(this.lineOffsetToPosition(fileName, replacementSpan.start),
this.lineOffsetToPosition(fileName, replacementSpan.end));
return { name, kind, kindModifiers, sortText, replacementSpan: convertedSpan };
const { name, kind, kindModifiers, sortText, replacementSpan } = entry;
return { name, kind, kindModifiers, sortText, replacementSpan: this.decodeSpan(replacementSpan, fileName) };
}
return entry as { name: string, kind: ScriptElementKind, kindModifiers: string, sortText: string };
@@ -230,13 +193,7 @@ namespace ts.server {
}
getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails {
const lineOffset = this.positionToOneBasedLineOffset(fileName, position);
const args: protocol.CompletionDetailsRequestArgs = {
file: fileName,
line: lineOffset.line,
offset: lineOffset.offset,
entryNames: [entryName]
};
const args: protocol.CompletionDetailsRequestArgs = { ...this.createFileLocationRequestArgs(fileName, position), entryNames: [entryName] };
const request = this.processRequest<protocol.CompletionDetailsRequest>(CommandNames.CompletionDetails, args);
const response = this.processResponse<protocol.CompletionDetailsResponse>(request);
@@ -257,55 +214,36 @@ namespace ts.server {
const request = this.processRequest<protocol.NavtoRequest>(CommandNames.Navto, args);
const response = this.processResponse<protocol.NavtoResponse>(request);
return response.body.map(entry => {
const fileName = entry.file;
const start = this.lineOffsetToPosition(fileName, entry.start);
const end = this.lineOffsetToPosition(fileName, entry.end);
return {
name: entry.name,
containerName: entry.containerName || "",
containerKind: entry.containerKind || ScriptElementKind.unknown,
kind: entry.kind,
kindModifiers: entry.kindModifiers,
matchKind: entry.matchKind,
isCaseSensitive: entry.isCaseSensitive,
fileName: fileName,
textSpan: ts.createTextSpanFromBounds(start, end)
};
});
return response.body.map(entry => ({
name: entry.name,
containerName: entry.containerName || "",
containerKind: entry.containerKind || ScriptElementKind.unknown,
kind: entry.kind,
kindModifiers: entry.kindModifiers,
matchKind: entry.matchKind,
isCaseSensitive: entry.isCaseSensitive,
fileName: entry.file,
textSpan: this.decodeSpan(entry),
}));
}
getFormattingEditsForRange(fileName: string, start: number, end: number, _options: ts.FormatCodeOptions): ts.TextChange[] {
const startLineOffset = this.positionToOneBasedLineOffset(fileName, start);
const endLineOffset = this.positionToOneBasedLineOffset(fileName, end);
const args: protocol.FormatRequestArgs = {
file: fileName,
line: startLineOffset.line,
offset: startLineOffset.offset,
endLine: endLineOffset.line,
endOffset: endLineOffset.offset,
};
getFormattingEditsForRange(file: string, start: number, end: number, _options: FormatCodeOptions): ts.TextChange[] {
const args: protocol.FormatRequestArgs = this.createFileLocationRequestArgsWithEndLineAndOffset(file, start, end);
// TODO: handle FormatCodeOptions
const request = this.processRequest<protocol.FormatRequest>(CommandNames.Format, args);
const response = this.processResponse<protocol.FormatResponse>(request);
return response.body.map(entry => this.convertCodeEditsToTextChange(fileName, entry));
return response.body.map(entry => this.convertCodeEditsToTextChange(file, entry));
}
getFormattingEditsForDocument(fileName: string, options: ts.FormatCodeOptions): ts.TextChange[] {
getFormattingEditsForDocument(fileName: string, options: FormatCodeOptions): ts.TextChange[] {
return this.getFormattingEditsForRange(fileName, 0, this.host.getScriptSnapshot(fileName).getLength(), options);
}
getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, _options: FormatCodeOptions): ts.TextChange[] {
const lineOffset = this.positionToOneBasedLineOffset(fileName, position);
const args: protocol.FormatOnKeyRequestArgs = {
file: fileName,
line: lineOffset.line,
offset: lineOffset.offset,
key: key
};
const args: protocol.FormatOnKeyRequestArgs = { ...this.createFileLocationRequestArgs(fileName, position), key };
// TODO: handle FormatCodeOptions
const request = this.processRequest<protocol.FormatOnKeyRequest>(CommandNames.Formatonkey, args);
@@ -315,79 +253,49 @@ namespace ts.server {
}
getDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[] {
const lineOffset = this.positionToOneBasedLineOffset(fileName, position);
const args: protocol.FileLocationRequestArgs = {
file: fileName,
line: lineOffset.line,
offset: lineOffset.offset,
};
const args: protocol.FileLocationRequestArgs = this.createFileLocationRequestArgs(fileName, position);
const request = this.processRequest<protocol.DefinitionRequest>(CommandNames.Definition, args);
const response = this.processResponse<protocol.DefinitionResponse>(request);
return response.body.map(entry => {
const fileName = entry.file;
const start = this.lineOffsetToPosition(fileName, entry.start);
const end = this.lineOffsetToPosition(fileName, entry.end);
return {
containerKind: ScriptElementKind.unknown,
containerName: "",
fileName: fileName,
textSpan: ts.createTextSpanFromBounds(start, end),
kind: ScriptElementKind.unknown,
name: ""
};
});
return response.body.map(entry => ({
containerKind: ScriptElementKind.unknown,
containerName: "",
fileName: entry.file,
textSpan: this.decodeSpan(entry),
kind: ScriptElementKind.unknown,
name: ""
}));
}
getTypeDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[] {
const lineOffset = this.positionToOneBasedLineOffset(fileName, position);
const args: protocol.FileLocationRequestArgs = {
file: fileName,
line: lineOffset.line,
offset: lineOffset.offset,
};
const args: protocol.FileLocationRequestArgs = this.createFileLocationRequestArgs(fileName, position);
const request = this.processRequest<protocol.TypeDefinitionRequest>(CommandNames.TypeDefinition, args);
const response = this.processResponse<protocol.TypeDefinitionResponse>(request);
return response.body.map(entry => {
const fileName = entry.file;
const start = this.lineOffsetToPosition(fileName, entry.start);
const end = this.lineOffsetToPosition(fileName, entry.end);
return {
containerKind: ScriptElementKind.unknown,
containerName: "",
fileName: fileName,
textSpan: ts.createTextSpanFromBounds(start, end),
kind: ScriptElementKind.unknown,
name: ""
};
});
return response.body.map(entry => ({
containerKind: ScriptElementKind.unknown,
containerName: "",
fileName: entry.file,
textSpan: this.decodeSpan(entry),
kind: ScriptElementKind.unknown,
name: ""
}));
}
getImplementationAtPosition(fileName: string, position: number): ImplementationLocation[] {
const lineOffset = this.positionToOneBasedLineOffset(fileName, position);
const args: protocol.FileLocationRequestArgs = {
file: fileName,
line: lineOffset.line,
offset: lineOffset.offset,
};
const args = this.createFileLocationRequestArgs(fileName, position);
const request = this.processRequest<protocol.ImplementationRequest>(CommandNames.Implementation, args);
const response = this.processResponse<protocol.ImplementationResponse>(request);
return response.body.map(entry => {
const fileName = entry.file;
const start = this.lineOffsetToPosition(fileName, entry.start);
const end = this.lineOffsetToPosition(fileName, entry.end);
return {
fileName,
textSpan: ts.createTextSpanFromBounds(start, end),
kind: ScriptElementKind.unknown,
displayParts: []
};
});
return response.body.map(entry => ({
fileName: entry.file,
textSpan: this.decodeSpan(entry),
kind: ScriptElementKind.unknown,
displayParts: []
}));
}
findReferences(_fileName: string, _position: number): ReferencedSymbol[] {
@@ -396,49 +304,39 @@ namespace ts.server {
}
getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[] {
const lineOffset = this.positionToOneBasedLineOffset(fileName, position);
const args: protocol.FileLocationRequestArgs = {
file: fileName,
line: lineOffset.line,
offset: lineOffset.offset,
};
const args = this.createFileLocationRequestArgs(fileName, position);
const request = this.processRequest<protocol.ReferencesRequest>(CommandNames.References, args);
const response = this.processResponse<protocol.ReferencesResponse>(request);
return response.body.refs.map(entry => {
const fileName = entry.file;
const start = this.lineOffsetToPosition(fileName, entry.start);
const end = this.lineOffsetToPosition(fileName, entry.end);
return {
fileName: fileName,
textSpan: ts.createTextSpanFromBounds(start, end),
isWriteAccess: entry.isWriteAccess,
isDefinition: entry.isDefinition,
};
});
return response.body.refs.map(entry => ({
fileName: entry.file,
textSpan: this.decodeSpan(entry),
isWriteAccess: entry.isWriteAccess,
isDefinition: entry.isDefinition,
}));
}
getEmitOutput(_fileName: string): EmitOutput {
return notImplemented();
}
getSyntacticDiagnostics(fileName: string): Diagnostic[] {
const args: protocol.SyntacticDiagnosticsSyncRequestArgs = { file: fileName, includeLinePosition: true };
getSyntacticDiagnostics(file: string): Diagnostic[] {
const args: protocol.SyntacticDiagnosticsSyncRequestArgs = { file, includeLinePosition: true };
const request = this.processRequest<protocol.SyntacticDiagnosticsSyncRequest>(CommandNames.SyntacticDiagnosticsSync, args);
const response = this.processResponse<protocol.SyntacticDiagnosticsSyncResponse>(request);
return (<protocol.DiagnosticWithLinePosition[]>response.body).map(entry => this.convertDiagnostic(entry, fileName));
return (<protocol.DiagnosticWithLinePosition[]>response.body).map(entry => this.convertDiagnostic(entry, file));
}
getSemanticDiagnostics(fileName: string): Diagnostic[] {
const args: protocol.SemanticDiagnosticsSyncRequestArgs = { file: fileName, includeLinePosition: true };
getSemanticDiagnostics(file: string): Diagnostic[] {
const args: protocol.SemanticDiagnosticsSyncRequestArgs = { file, includeLinePosition: true };
const request = this.processRequest<protocol.SemanticDiagnosticsSyncRequest>(CommandNames.SemanticDiagnosticsSync, args);
const response = this.processResponse<protocol.SemanticDiagnosticsSyncResponse>(request);
return (<protocol.DiagnosticWithLinePosition[]>response.body).map(entry => this.convertDiagnostic(entry, fileName));
return (<protocol.DiagnosticWithLinePosition[]>response.body).map(entry => this.convertDiagnostic(entry, file));
}
convertDiagnostic(entry: protocol.DiagnosticWithLinePosition, _fileName: string): Diagnostic {
@@ -456,7 +354,7 @@ namespace ts.server {
start: entry.start,
length: entry.length,
messageText: entry.message,
category: category,
category,
code: entry.code
};
}
@@ -466,29 +364,18 @@ namespace ts.server {
}
getRenameInfo(fileName: string, position: number, findInStrings?: boolean, findInComments?: boolean): RenameInfo {
const lineOffset = this.positionToOneBasedLineOffset(fileName, position);
const args: protocol.RenameRequestArgs = {
file: fileName,
line: lineOffset.line,
offset: lineOffset.offset,
findInStrings,
findInComments
};
const args: protocol.RenameRequestArgs = { ...this.createFileLocationRequestArgs(fileName, position), findInStrings, findInComments };
const request = this.processRequest<protocol.RenameRequest>(CommandNames.Rename, args);
const response = this.processResponse<protocol.RenameResponse>(request);
const locations: RenameLocation[] = [];
response.body.locs.map((entry: protocol.SpanGroup) => {
for (const entry of response.body.locs) {
const fileName = entry.file;
entry.locs.map((loc: protocol.TextSpan) => {
const start = this.lineOffsetToPosition(fileName, loc.start);
const end = this.lineOffsetToPosition(fileName, loc.end);
locations.push({
textSpan: ts.createTextSpanFromBounds(start, end),
fileName: fileName
});
});
});
for (const loc of entry.locs) {
locations.push({ textSpan: this.decodeSpan(loc, fileName), fileName });
}
}
return this.lastRenameEntry = {
canRename: response.body.info.canRename,
displayName: response.body.info.displayName,
@@ -496,12 +383,12 @@ namespace ts.server {
kind: response.body.info.kind,
kindModifiers: response.body.info.kindModifiers,
localizedErrorMessage: response.body.info.localizedErrorMessage,
triggerSpan: ts.createTextSpanFromBounds(position, position),
fileName: fileName,
position: position,
findInStrings: findInStrings,
findInComments: findInComments,
locations: locations
triggerSpan: createTextSpanFromBounds(position, position),
fileName,
position,
findInStrings,
findInComments,
locations,
};
}
@@ -534,12 +421,12 @@ namespace ts.server {
}));
}
getNavigationBarItems(fileName: string): NavigationBarItem[] {
const request = this.processRequest<protocol.NavBarRequest>(CommandNames.NavBar, { file: fileName });
getNavigationBarItems(file: string): NavigationBarItem[] {
const request = this.processRequest<protocol.NavBarRequest>(CommandNames.NavBar, { file });
const response = this.processResponse<protocol.NavBarResponse>(request);
const lineMap = this.getLineMap(fileName);
return this.decodeNavigationBarItems(response.body, fileName, lineMap);
const lineMap = this.getLineMap(file);
return this.decodeNavigationBarItems(response.body, file, lineMap);
}
private decodeNavigationTree(tree: protocol.NavigationTree, fileName: string, lineMap: number[]): NavigationTree {
@@ -552,15 +439,19 @@ namespace ts.server {
};
}
getNavigationTree(fileName: string): NavigationTree {
const request = this.processRequest<protocol.NavTreeRequest>(CommandNames.NavTree, { file: fileName });
getNavigationTree(file: string): NavigationTree {
const request = this.processRequest<protocol.NavTreeRequest>(CommandNames.NavTree, { file });
const response = this.processResponse<protocol.NavTreeResponse>(request);
const lineMap = this.getLineMap(fileName);
return this.decodeNavigationTree(response.body, fileName, lineMap);
const lineMap = this.getLineMap(file);
return this.decodeNavigationTree(response.body, file, lineMap);
}
private decodeSpan(span: protocol.TextSpan, fileName: string, lineMap: number[]) {
private decodeSpan(span: protocol.TextSpan & { file: string }): TextSpan;
private decodeSpan(span: protocol.TextSpan, fileName: string, lineMap?: number[]): TextSpan;
private decodeSpan(span: protocol.TextSpan & { file: string }, fileName?: string, lineMap?: number[]): TextSpan {
fileName = fileName || span.file;
lineMap = lineMap || this.getLineMap(fileName);
return createTextSpanFromBounds(
this.lineOffsetToPosition(fileName, span.start, lineMap),
this.lineOffsetToPosition(fileName, span.end, lineMap));
@@ -575,12 +466,7 @@ namespace ts.server {
}
getSignatureHelpItems(fileName: string, position: number): SignatureHelpItems {
const lineOffset = this.positionToOneBasedLineOffset(fileName, position);
const args: protocol.SignatureHelpRequestArgs = {
file: fileName,
line: lineOffset.line,
offset: lineOffset.offset
};
const args: protocol.SignatureHelpRequestArgs = this.createFileLocationRequestArgs(fileName, position);
const request = this.processRequest<protocol.SignatureHelpRequest>(CommandNames.SignatureHelp, args);
const response = this.processResponse<protocol.SignatureHelpResponse>(request);
@@ -589,75 +475,40 @@ namespace ts.server {
return undefined;
}
const helpItems: protocol.SignatureHelpItems = response.body;
const span = helpItems.applicableSpan;
const start = this.lineOffsetToPosition(fileName, span.start);
const end = this.lineOffsetToPosition(fileName, span.end);
const { items, applicableSpan: encodedApplicableSpan, selectedItemIndex, argumentIndex, argumentCount } = response.body;
const result: SignatureHelpItems = {
items: helpItems.items,
applicableSpan: {
start: start,
length: end - start
},
selectedItemIndex: helpItems.selectedItemIndex,
argumentIndex: helpItems.argumentIndex,
argumentCount: helpItems.argumentCount,
};
return result;
const applicableSpan = this.decodeSpan(encodedApplicableSpan, fileName);
return { items, applicableSpan, selectedItemIndex, argumentIndex, argumentCount };
}
getOccurrencesAtPosition(fileName: string, position: number): ReferenceEntry[] {
const lineOffset = this.positionToOneBasedLineOffset(fileName, position);
const args: protocol.FileLocationRequestArgs = {
file: fileName,
line: lineOffset.line,
offset: lineOffset.offset,
};
const args = this.createFileLocationRequestArgs(fileName, position);
const request = this.processRequest<protocol.OccurrencesRequest>(CommandNames.Occurrences, args);
const response = this.processResponse<protocol.OccurrencesResponse>(request);
return response.body.map(entry => {
const fileName = entry.file;
const start = this.lineOffsetToPosition(fileName, entry.start);
const end = this.lineOffsetToPosition(fileName, entry.end);
return {
fileName,
textSpan: ts.createTextSpanFromBounds(start, end),
isWriteAccess: entry.isWriteAccess,
isDefinition: false
};
});
return response.body.map(entry => ({
fileName: entry.file,
textSpan: this.decodeSpan(entry),
isWriteAccess: entry.isWriteAccess,
isDefinition: false
}));
}
getDocumentHighlights(fileName: string, position: number, filesToSearch: string[]): DocumentHighlights[] {
const { line, offset } = this.positionToOneBasedLineOffset(fileName, position);
const args: protocol.DocumentHighlightsRequestArgs = { file: fileName, line, offset, filesToSearch };
const args: protocol.DocumentHighlightsRequestArgs = { ...this.createFileLocationRequestArgs(fileName, position), filesToSearch };
const request = this.processRequest<protocol.DocumentHighlightsRequest>(CommandNames.DocumentHighlights, args);
const response = this.processResponse<protocol.DocumentHighlightsResponse>(request);
const self = this;
return response.body.map(convertToDocumentHighlights);
function convertToDocumentHighlights(item: ts.server.protocol.DocumentHighlightsItem): ts.DocumentHighlights {
const { file, highlightSpans } = item;
return {
fileName: file,
highlightSpans: highlightSpans.map(convertHighlightSpan)
};
function convertHighlightSpan(span: ts.server.protocol.HighlightSpan): ts.HighlightSpan {
const start = self.lineOffsetToPosition(file, span.start);
const end = self.lineOffsetToPosition(file, span.end);
return {
textSpan: ts.createTextSpanFromBounds(start, end),
kind: span.kind
};
}
}
return response.body.map(item => ({
fileName: item.file,
highlightSpans: item.highlightSpans.map(span => ({
textSpan: this.decodeSpan(span, item.file),
kind: span.kind
})),
}));
}
getOutliningSpans(_fileName: string): OutliningSpan[] {
@@ -680,39 +531,36 @@ namespace ts.server {
return notImplemented();
}
getCodeFixesAtPosition(fileName: string, start: number, end: number, errorCodes: number[]): CodeAction[] {
const startLineOffset = this.positionToOneBasedLineOffset(fileName, start);
const endLineOffset = this.positionToOneBasedLineOffset(fileName, end);
const args: protocol.CodeFixRequestArgs = {
file: fileName,
startLine: startLineOffset.line,
startOffset: startLineOffset.offset,
endLine: endLineOffset.line,
endOffset: endLineOffset.offset,
errorCodes: errorCodes,
};
getCodeFixesAtPosition(file: string, start: number, end: number, errorCodes: number[]): CodeAction[] {
const args: protocol.CodeFixRequestArgs = { ...this.createFileRangeRequestArgs(file, start, end), errorCodes };
const request = this.processRequest<protocol.CodeFixRequest>(CommandNames.GetCodeFixes, args);
const response = this.processResponse<protocol.CodeFixResponse>(request);
return response.body.map(entry => this.convertCodeActions(entry, fileName));
return response.body.map(entry => this.convertCodeActions(entry, file));
}
private createFileLocationOrRangeRequestArgs(positionOrRange: number | TextRange, fileName: string): protocol.FileLocationOrRangeRequestArgs {
if (typeof positionOrRange === "number") {
const { line, offset } = this.positionToOneBasedLineOffset(fileName, positionOrRange);
return <protocol.FileLocationRequestArgs>{ file: fileName, line, offset };
}
const { line: startLine, offset: startOffset } = this.positionToOneBasedLineOffset(fileName, positionOrRange.pos);
const { line: endLine, offset: endOffset } = this.positionToOneBasedLineOffset(fileName, positionOrRange.end);
return <protocol.FileRangeRequestArgs>{
file: fileName,
startLine,
startOffset,
endLine,
endOffset
};
return typeof positionOrRange === "number"
? this.createFileLocationRequestArgs(fileName, positionOrRange)
: this.createFileRangeRequestArgs(fileName, positionOrRange.pos, positionOrRange.end);
}
private createFileLocationRequestArgs(file: string, position: number): protocol.FileLocationRequestArgs {
const { line, offset } = this.positionToOneBasedLineOffset(file, position);
return { file, line, offset };
}
private createFileRangeRequestArgs(file: string, start: number, end: number): protocol.FileRangeRequestArgs {
const { line: startLine, offset: startOffset } = this.positionToOneBasedLineOffset(file, start);
const { line: endLine, offset: endOffset } = this.positionToOneBasedLineOffset(file, end);
return { file, startLine, startOffset, endLine, endOffset };
}
private createFileLocationRequestArgsWithEndLineAndOffset(file: string, start: number, end: number): protocol.FileLocationRequestArgs & { endLine: number, endOffset: number } {
const { line, offset } = this.positionToOneBasedLineOffset(file, start);
const { line: endLine, offset: endOffset } = this.positionToOneBasedLineOffset(file, end);
return { file, line, offset, endLine, endOffset };
}
getApplicableRefactors(fileName: string, positionOrRange: number | TextRange): ApplicableRefactorInfo[] {
@@ -723,20 +571,49 @@ namespace ts.server {
return response.body;
}
getRefactorCodeActions(
getEditsForRefactor(
fileName: string,
_formatOptions: FormatCodeSettings,
positionOrRange: number | TextRange,
refactorName: string) {
refactorName: string,
actionName: string): RefactorEditInfo {
const args = this.createFileLocationOrRangeRequestArgs(positionOrRange, fileName) as protocol.GetRefactorCodeActionsRequestArgs;
args.refactorName = refactorName;
const args = this.createFileLocationOrRangeRequestArgs(positionOrRange, fileName) as protocol.GetEditsForRefactorRequestArgs;
args.refactor = refactorName;
args.action = actionName;
const request = this.processRequest<protocol.GetRefactorCodeActionsRequest>(CommandNames.GetRefactorCodeActions, args);
const response = this.processResponse<protocol.GetRefactorCodeActionsResponse>(request);
const codeActions = response.body.actions;
const request = this.processRequest<protocol.GetEditsForRefactorRequest>(CommandNames.GetEditsForRefactor, args);
const response = this.processResponse<protocol.GetEditsForRefactorResponse>(request);
return map(codeActions, codeAction => this.convertCodeActions(codeAction, fileName));
if (!response.body) {
return {
edits: []
};
}
const edits: FileTextChanges[] = this.convertCodeEditsToTextChanges(response.body.edits);
const renameFilename: string | undefined = response.body.renameFilename;
let renameLocation: number | undefined = undefined;
if (renameFilename !== undefined) {
renameLocation = this.lineOffsetToPosition(renameFilename, response.body.renameLocation);
}
return {
edits,
renameFilename,
renameLocation
};
}
private convertCodeEditsToTextChanges(edits: protocol.FileCodeEdits[]): FileTextChanges[] {
return edits.map(edit => {
const fileName = edit.fileName;
return {
fileName,
textChanges: edit.textChanges.map(t => this.convertTextChangeToCodeEdit(t, fileName))
};
});
}
convertCodeActions(entry: protocol.CodeAction, fileName: string): CodeAction {
@@ -750,37 +627,19 @@ namespace ts.server {
}
convertTextChangeToCodeEdit(change: protocol.CodeEdit, fileName: string): ts.TextChange {
const start = this.lineOffsetToPosition(fileName, change.start);
const end = this.lineOffsetToPosition(fileName, change.end);
return {
span: {
start: start,
length: end - start
},
span: this.decodeSpan(change, fileName),
newText: change.newText ? change.newText : ""
};
}
getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[] {
const lineOffset = this.positionToOneBasedLineOffset(fileName, position);
const args: protocol.FileLocationRequestArgs = {
file: fileName,
line: lineOffset.line,
offset: lineOffset.offset,
};
const args = this.createFileLocationRequestArgs(fileName, position);
const request = this.processRequest<protocol.BraceRequest>(CommandNames.Brace, args);
const response = this.processResponse<protocol.BraceResponse>(request);
return response.body.map(entry => {
const start = this.lineOffsetToPosition(fileName, entry.start);
const end = this.lineOffsetToPosition(fileName, entry.end);
return {
start: start,
length: end - start,
};
});
return response.body.map(entry => this.decodeSpan(entry, fileName));
}
getIndentationAtPosition(_fileName: string, _position: number, _options: EditorOptions): number {
+83 -52
View File
@@ -37,13 +37,15 @@ namespace ts.server {
}
export interface ProjectInfoTelemetryEventData {
/** Cryptographically secure hash of project file location. */
readonly projectId: string;
/** Count of file extensions seen in the project. */
readonly fileStats: FileStats;
/**
* Any compiler options that might contain paths will be taken out.
* Enum compiler options will be converted to strings.
*/
readonly compilerOptions: ts.CompilerOptions;
readonly compilerOptions: CompilerOptions;
// "extends", "files", "include", or "exclude" will be undefined if an external config is used.
// Otherwise, we will use "true" if the property is present and "false" if it is missing.
readonly extends: boolean | undefined;
@@ -199,7 +201,7 @@ namespace ts.server {
* This helper function processes a list of projects and return the concatenated, sortd and deduplicated output of processing each project.
*/
export function combineProjectOutput<T>(projects: Project[], action: (project: Project) => T[], comparer?: (a: T, b: T) => number, areEqual?: (a: T, b: T) => boolean) {
const result = projects.reduce<T[]>((previous, current) => concatenate(previous, action(current)), []).sort(comparer);
const result = flatMap(projects, action).sort(comparer);
return projects.length > 1 ? deduplicate(result, areEqual) : result;
}
@@ -237,10 +239,7 @@ namespace ts.server {
const fileNamePropertyReader: FilePropertyReader<string> = {
getFileName: x => x,
getScriptKind: _ => undefined,
hasMixedContent: (fileName, extraFileExtensions) => {
const mixedContentExtensions = ts.map(ts.filter(extraFileExtensions, item => item.isMixedContent), item => item.extension);
return forEach(mixedContentExtensions, extension => fileExtensionIs(fileName, extension));
}
hasMixedContent: (fileName, extraFileExtensions) => some(extraFileExtensions, ext => ext.isMixedContent && fileExtensionIs(fileName, ext.extension)),
};
const externalFilePropertyReader: FilePropertyReader<protocol.ExternalFile> = {
@@ -341,7 +340,7 @@ namespace ts.server {
/**
* Container of all known scripts
*/
private readonly filenameToScriptInfo = createFileMap<ScriptInfo>();
private readonly filenameToScriptInfo = createMap<ScriptInfo>();
/**
* maps external project file name to list of config files that were the part of this project
*/
@@ -371,7 +370,7 @@ namespace ts.server {
private readonly throttledOperations: ThrottledOperations;
private readonly hostConfiguration: HostConfiguration;
private static safelist: SafeList = defaultTypeSafeList;
private safelist: SafeList = defaultTypeSafeList;
private changedFiles: ScriptInfo[];
@@ -563,10 +562,17 @@ namespace ts.server {
}
else {
if (info && (!info.isScriptOpen())) {
// file has been changed which might affect the set of referenced files in projects that include
// this file and set of inferred projects
info.reloadFromFile();
this.updateProjectGraphs(info.containingProjects);
if (info.containingProjects.length === 0) {
// Orphan script info, remove it as we can always reload it on next open
info.stopWatcher();
this.filenameToScriptInfo.delete(info.path);
}
else {
// file has been changed which might affect the set of referenced files in projects that include
// this file and set of inferred projects
info.reloadFromFile();
this.updateProjectGraphs(info.containingProjects);
}
}
}
}
@@ -579,7 +585,7 @@ namespace ts.server {
// TODO: handle isOpen = true case
if (!info.isScriptOpen()) {
this.filenameToScriptInfo.remove(info.path);
this.filenameToScriptInfo.delete(info.path);
this.lastDeletedFile = info;
// capture list of projects since detachAllProjects will wipe out original list
@@ -624,7 +630,7 @@ namespace ts.server {
// If a change was made inside "folder/file", node will trigger the callback twice:
// one with the fileName being "folder/file", and the other one with "folder".
// We don't respond to the second one.
if (fileName && !ts.isSupportedSourceFileName(fileName, project.getCompilerOptions(), this.hostConfiguration.extraFileExtensions)) {
if (fileName && !isSupportedSourceFileName(fileName, project.getCompilerOptions(), this.hostConfiguration.extraFileExtensions)) {
return;
}
@@ -694,15 +700,15 @@ namespace ts.server {
switch (project.projectKind) {
case ProjectKind.External:
removeItemFromSet(this.externalProjects, <ExternalProject>project);
unorderedRemoveItem(this.externalProjects, <ExternalProject>project);
this.projectToSizeMap.delete((project as ExternalProject).externalProjectName);
break;
case ProjectKind.Configured:
removeItemFromSet(this.configuredProjects, <ConfiguredProject>project);
unorderedRemoveItem(this.configuredProjects, <ConfiguredProject>project);
this.projectToSizeMap.delete((project as ConfiguredProject).canonicalConfigFilePath);
break;
case ProjectKind.Inferred:
removeItemFromSet(this.inferredProjects, <InferredProject>project);
unorderedRemoveItem(this.inferredProjects, <InferredProject>project);
break;
}
}
@@ -781,7 +787,7 @@ namespace ts.server {
// to the disk, and the server's version of the file can be out of sync.
info.close();
removeItemFromSet(this.openFiles, info);
unorderedRemoveItem(this.openFiles, info);
// collect all projects that should be removed
let projectsToRemove: Project[];
@@ -827,11 +833,29 @@ namespace ts.server {
this.assignScriptInfoToInferredProjectIfNecessary(f, /*addToListOfOpenFiles*/ false);
}
}
// Cleanup script infos that arent part of any project is postponed to
// next file open so that if file from same project is opened we wont end up creating same script infos
}
if (info.containingProjects.length === 0) {
// if there are not projects that include this script info - delete it
this.filenameToScriptInfo.remove(info.path);
// If the current info is being just closed - add the watcher file to track changes
// But if file was deleted, handle that part
if (this.host.fileExists(info.fileName)) {
this.watchClosedScriptInfo(info);
}
else {
this.handleDeletedFile(info);
}
}
private deleteOrphanScriptInfoNotInAnyProject() {
this.filenameToScriptInfo.forEach(info => {
if (!info.isScriptOpen() && info.containingProjects.length === 0) {
// if there are not projects that include this script info - delete it
info.stopWatcher();
this.filenameToScriptInfo.delete(info.path);
}
});
}
/**
@@ -913,7 +937,7 @@ namespace ts.server {
this.logger.info("Open files: ");
for (const rootFile of this.openFiles) {
this.logger.info(rootFile.fileName);
this.logger.info(`\t${rootFile.fileName}`);
}
this.logger.endGroup();
@@ -948,20 +972,14 @@ namespace ts.server {
configFilename = normalizePath(configFilename);
const configFileContent = this.host.readFile(configFilename);
let errors: Diagnostic[];
const result = parseConfigFileTextToJson(configFilename, configFileContent);
let config = result.config;
if (result.error) {
// try to reparse config file
const { configJsonObject: sanitizedConfig, diagnostics } = sanitizeConfigFile(configFilename, configFileContent);
config = sanitizedConfig;
errors = diagnostics.length ? diagnostics : [result.error];
const result = parseJsonText(configFilename, configFileContent);
if (!result.endOfFileToken) {
result.endOfFileToken = <EndOfFileToken>{ kind: SyntaxKind.EndOfFileToken };
}
const parsedCommandLine = parseJsonConfigFileContent(
config,
const errors = result.parseDiagnostics;
const parsedCommandLine = parseJsonSourceFileConfigFileContent(
result,
this.host,
getDirectoryPath(configFilename),
/*existingOptions*/ {},
@@ -970,23 +988,23 @@ namespace ts.server {
this.hostConfiguration.extraFileExtensions);
if (parsedCommandLine.errors.length) {
errors = concatenate(errors, parsedCommandLine.errors);
errors.push(...parsedCommandLine.errors);
}
Debug.assert(!!parsedCommandLine.fileNames);
if (parsedCommandLine.fileNames.length === 0) {
(errors || (errors = [])).push(createCompilerDiagnostic(Diagnostics.The_config_file_0_found_doesn_t_contain_any_source_files, configFilename));
errors.push(createCompilerDiagnostic(Diagnostics.The_config_file_0_found_doesn_t_contain_any_source_files, configFilename));
return { success: false, configFileErrors: errors };
}
const projectOptions: ProjectOptions = {
files: parsedCommandLine.fileNames,
compilerOptions: parsedCommandLine.options,
configHasExtendsProperty: config.extends !== undefined,
configHasFilesProperty: config.files !== undefined,
configHasIncludeProperty: config.include !== undefined,
configHasExcludeProperty: config.exclude !== undefined,
configHasExtendsProperty: parsedCommandLine.raw["extends"] !== undefined,
configHasFilesProperty: parsedCommandLine.raw["files"] !== undefined,
configHasIncludeProperty: parsedCommandLine.raw["include"] !== undefined,
configHasExcludeProperty: parsedCommandLine.raw["exclude"] !== undefined,
wildcardDirectories: createMapFromTemplate(parsedCommandLine.wildcardDirectories),
typeAcquisition: parsedCommandLine.typeAcquisition,
compileOnSave: parsedCommandLine.compileOnSave
@@ -1049,6 +1067,7 @@ namespace ts.server {
if (!this.eventHandler) return;
const data: ProjectInfoTelemetryEventData = {
projectId: this.host.createHash(projectKey),
fileStats: countEachFileTypes(project.getScriptInfos()),
compilerOptions: convertCompilerOptionsForTelemetry(project.getCompilerOptions()),
typeAcquisition: convertTypeAcquisition(project.getTypeAcquisition()),
@@ -1060,7 +1079,7 @@ namespace ts.server {
configFileName: configFileName(),
projectType: project instanceof server.ExternalProject ? "external" : "configured",
languageServiceEnabled: project.languageServiceEnabled,
version: ts.version,
version,
};
this.eventHandler({ eventName: ProjectInfoTelemetryEvent, data });
@@ -1070,7 +1089,7 @@ namespace ts.server {
}
const configFilePath = project instanceof server.ConfiguredProject && project.getConfigFilePath();
const base = ts.getBaseFileName(configFilePath);
const base = getBaseFileName(configFilePath);
return base === "tsconfig.json" || base === "jsconfig.json" ? base : "other";
}
@@ -1154,7 +1173,7 @@ namespace ts.server {
return {
success: conversionResult.success,
project,
errors: project.getProjectErrors()
errors: project.getGlobalProjectErrors()
};
}
@@ -1300,6 +1319,14 @@ namespace ts.server {
return this.getScriptInfoForNormalizedPath(toNormalizedPath(uncheckedFileName));
}
watchClosedScriptInfo(info: ScriptInfo) {
// do not watch files with mixed content - server doesn't know how to interpret it
if (!info.hasMixedContent) {
const { fileName } = info;
info.setWatcher(this.host.watchFile(fileName, _ => this.onSourceFileChanged(fileName)));
}
}
getOrCreateScriptInfoForNormalizedPath(fileName: NormalizedPath, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind, hasMixedContent?: boolean) {
let info = this.getScriptInfoForNormalizedPath(fileName);
if (!info) {
@@ -1315,15 +1342,13 @@ namespace ts.server {
}
}
else {
// do not watch files with mixed content - server doesn't know how to interpret it
if (!hasMixedContent) {
info.setWatcher(this.host.watchFile(fileName, _ => this.onSourceFileChanged(fileName)));
}
this.watchClosedScriptInfo(info);
}
}
}
if (info) {
if (openedByClient && !info.isScriptOpen()) {
info.stopWatcher();
info.open(fileContent);
if (hasMixedContent) {
info.registerFileUpdate();
@@ -1418,6 +1443,7 @@ namespace ts.server {
for (const p of this.inferredProjects) {
p.updateGraph();
}
this.printProjects();
}
@@ -1451,6 +1477,11 @@ namespace ts.server {
// at this point if file is the part of some configured/external project then this project should be created
const info = this.getOrCreateScriptInfoForNormalizedPath(fileName, /*openedByClient*/ true, fileContent, scriptKind, hasMixedContent);
this.assignScriptInfoToInferredProjectIfNecessary(info, /*addToListOfOpenFiles*/ true);
// Delete the orphan files here because there might be orphan script infos (which are not part of project)
// when some file/s were closed which resulted in project removal.
// It was then postponed to cleanup these script infos so that they can be reused if
// the file from that old project is reopened because of opening file from here.
this.deleteOrphanScriptInfoNotInAnyProject();
this.printProjects();
return { configFileName, configFileErrors };
}
@@ -1583,13 +1614,13 @@ namespace ts.server {
}
/** Makes a filename safe to insert in a RegExp */
private static filenameEscapeRegexp = /[-\/\\^$*+?.()|[\]{}]/g;
private static readonly filenameEscapeRegexp = /[-\/\\^$*+?.()|[\]{}]/g;
private static escapeFilenameForRegex(filename: string) {
return filename.replace(this.filenameEscapeRegexp, "\\$&");
}
resetSafeList(): void {
ProjectService.safelist = defaultTypeSafeList;
this.safelist = defaultTypeSafeList;
}
loadSafeList(fileName: string): void {
@@ -1599,7 +1630,7 @@ namespace ts.server {
raw[k].match = new RegExp(raw[k].match as {} as string, "i");
}
// raw is now fixed and ready
ProjectService.safelist = raw;
this.safelist = raw;
}
applySafeList(proj: protocol.ExternalProject): void {
@@ -1610,8 +1641,8 @@ namespace ts.server {
const normalizedNames = rootFiles.map(f => normalizeSlashes(f.fileName));
for (const name of Object.keys(ProjectService.safelist)) {
const rule = ProjectService.safelist[name];
for (const name of Object.keys(this.safelist)) {
const rule = this.safelist[name];
for (const root of normalizedNames) {
if (rule.match.test(root)) {
this.logger.info(`Excluding files based on rule ${name}`);
+28 -20
View File
@@ -3,21 +3,21 @@
/// <reference path="scriptInfo.ts" />
namespace ts.server {
export class LSHost implements ts.LanguageServiceHost, ModuleResolutionHost {
private compilationSettings: ts.CompilerOptions;
private readonly resolvedModuleNames = createFileMap<Map<ResolvedModuleWithFailedLookupLocations>>();
private readonly resolvedTypeReferenceDirectives = createFileMap<Map<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>>();
export class LSHost implements LanguageServiceHost, ModuleResolutionHost {
private compilationSettings: CompilerOptions;
private readonly resolvedModuleNames = createMap<Map<ResolvedModuleWithFailedLookupLocations>>();
private readonly resolvedTypeReferenceDirectives = createMap<Map<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>>();
private readonly getCanonicalFileName: (fileName: string) => string;
private filesWithChangedSetOfUnresolvedImports: Path[];
private readonly resolveModuleName: typeof resolveModuleName;
private resolveModuleName: typeof resolveModuleName;
readonly trace: (s: string) => void;
readonly realpath?: (path: string) => string;
constructor(private readonly host: ServerHost, private readonly project: Project, private readonly cancellationToken: HostCancellationToken) {
constructor(private readonly host: ServerHost, private project: Project, private readonly cancellationToken: HostCancellationToken) {
this.cancellationToken = new ThrottledCancellationToken(cancellationToken, project.projectService.throttleWaitMilliseconds);
this.getCanonicalFileName = ts.createGetCanonicalFileName(this.host.useCaseSensitiveFileNames);
this.getCanonicalFileName = createGetCanonicalFileName(this.host.useCaseSensitiveFileNames);
if (host.trace) {
this.trace = s => host.trace(s);
@@ -29,7 +29,7 @@ namespace ts.server {
: undefined;
const primaryResult = resolveModuleName(moduleName, containingFile, compilerOptions, host);
// return result immediately only if it is .ts, .tsx or .d.ts
if (moduleHasNonRelativeName(moduleName) && !(primaryResult.resolvedModule && extensionIsTypeScript(primaryResult.resolvedModule.extension)) && globalCache !== undefined) {
if (!isExternalModuleNameRelative(moduleName) && !(primaryResult.resolvedModule && extensionIsTypeScript(primaryResult.resolvedModule.extension)) && globalCache !== undefined) {
// otherwise try to load typings from @types
// create different collection of failed lookup locations for second pass
@@ -47,6 +47,11 @@ namespace ts.server {
}
}
dispose() {
this.project = undefined;
this.resolveModuleName = undefined;
}
public startRecordingFilesWithChangedResolutions() {
this.filesWithChangedSetOfUnresolvedImports = [];
}
@@ -60,7 +65,7 @@ namespace ts.server {
private resolveNamesWithLocalCache<T extends { failedLookupLocations: string[] }, R>(
names: string[],
containingFile: string,
cache: ts.FileMap<Map<T>>,
cache: Map<Map<T>>,
loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost) => T,
getResult: (s: T) => R,
getResultFileName: (result: R) => string | undefined,
@@ -94,7 +99,7 @@ namespace ts.server {
}
}
ts.Debug.assert(resolution !== undefined);
Debug.assert(resolution !== undefined);
resolvedModules.push(getResult(resolution));
}
@@ -172,7 +177,7 @@ namespace ts.server {
return combinePaths(nodeModuleBinDir, getDefaultLibFileName(this.compilationSettings));
}
getScriptSnapshot(filename: string): ts.IScriptSnapshot {
getScriptSnapshot(filename: string): IScriptSnapshot {
const scriptInfo = this.project.getScriptInfoLSHost(filename);
if (scriptInfo) {
return scriptInfo.getSnapshot();
@@ -205,11 +210,14 @@ namespace ts.server {
return this.host.resolvePath(path);
}
fileExists(path: string): boolean {
return this.host.fileExists(path);
fileExists(file: string): boolean {
// As an optimization, don't hit the disks for files we already know don't exist
// (because we're watching for their creation).
const path = toPath(file, this.host.getCurrentDirectory(), this.getCanonicalFileName);
return !this.project.isWatchedMissingFile(path) && this.host.fileExists(file);
}
readFile(fileName: string): string {
readFile(fileName: string): string | undefined {
return this.host.readFile(fileName);
}
@@ -217,8 +225,8 @@ namespace ts.server {
return this.host.directoryExists(path);
}
readDirectory(path: string, extensions?: string[], exclude?: string[], include?: string[]): string[] {
return this.host.readDirectory(path, extensions, exclude, include);
readDirectory(path: string, extensions?: ReadonlyArray<string>, exclude?: ReadonlyArray<string>, include?: ReadonlyArray<string>, depth?: number): string[] {
return this.host.readDirectory(path, extensions, exclude, include, depth);
}
getDirectories(path: string): string[] {
@@ -226,11 +234,11 @@ namespace ts.server {
}
notifyFileRemoved(info: ScriptInfo) {
this.resolvedModuleNames.remove(info.path);
this.resolvedTypeReferenceDirectives.remove(info.path);
this.resolvedModuleNames.delete(info.path);
this.resolvedTypeReferenceDirectives.delete(info.path);
}
setCompilationSettings(opt: ts.CompilerOptions) {
setCompilationSettings(opt: CompilerOptions) {
if (changesAffectModuleResolution(this.compilationSettings, opt)) {
this.resolvedModuleNames.clear();
this.resolvedTypeReferenceDirectives.clear();
@@ -238,4 +246,4 @@ namespace ts.server {
this.compilationSettings = opt;
}
}
}
}
+126 -42
View File
@@ -25,7 +25,7 @@ namespace ts.server {
result.jsx += 1;
break;
case ScriptKind.TS:
fileExtensionIs(info.fileName, ".d.ts")
fileExtensionIs(info.fileName, Extension.Dts)
? result.dts += 1
: result.ts += 1;
break;
@@ -58,7 +58,7 @@ namespace ts.server {
}
export class UnresolvedImportsMap {
readonly perFileMap = createFileMap<ReadonlyArray<string>>();
readonly perFileMap = createMap<ReadonlyArray<string>>();
private version = 0;
public clear() {
@@ -71,7 +71,7 @@ namespace ts.server {
}
public remove(path: Path) {
this.perFileMap.remove(path);
this.perFileMap.delete(path);
this.version++;
}
@@ -104,9 +104,10 @@ namespace ts.server {
export abstract class Project {
private rootFiles: ScriptInfo[] = [];
private rootFilesMap: FileMap<ScriptInfo> = createFileMap<ScriptInfo>();
private program: ts.Program;
private rootFilesMap: Map<ScriptInfo> = createMap<ScriptInfo>();
private program: Program;
private externalFiles: SortedReadonlyArray<string>;
private missingFilesMap: Map<FileWatcher> = createMap<FileWatcher>();
private cachedUnresolvedImportsPerFile = new UnresolvedImportsMap();
private lastCachedUnresolvedImportsList: SortedReadonlyArray<string>;
@@ -116,17 +117,17 @@ namespace ts.server {
public languageServiceEnabled = true;
protected readonly lsHost: LSHost;
protected lsHost: LSHost;
builder: Builder;
/**
* Set of files names that were updated since the last call to getChangesSinceVersion.
*/
private updatedFileNames: Map<string>;
private updatedFileNames: Map<true>;
/**
* Set of files that was returned from the last call to getChangesSinceVersion.
*/
private lastReportedFileNames: Map<string>;
private lastReportedFileNames: Map<true>;
/**
* Last version that was reported.
*/
@@ -179,14 +180,14 @@ namespace ts.server {
private readonly projectName: string,
readonly projectKind: ProjectKind,
readonly projectService: ProjectService,
private documentRegistry: ts.DocumentRegistry,
private documentRegistry: DocumentRegistry,
hasExplicitListOfFiles: boolean,
languageServiceEnabled: boolean,
private compilerOptions: CompilerOptions,
public compileOnSaveEnabled: boolean) {
if (!this.compilerOptions) {
this.compilerOptions = ts.getDefaultCompilerOptions();
this.compilerOptions = getDefaultCompilerOptions();
this.compilerOptions.allowNonTsExtensions = true;
this.compilerOptions.allowJs = true;
}
@@ -200,7 +201,7 @@ namespace ts.server {
this.lsHost = new LSHost(this.projectService.host, this, this.projectService.cancellationToken);
this.lsHost.setCompilationSettings(this.compilerOptions);
this.languageService = ts.createLanguageService(this.lsHost, this.documentRegistry);
this.languageService = createLanguageService(this.lsHost, this.documentRegistry);
if (!languageServiceEnabled) {
this.disableLanguageService();
@@ -216,7 +217,14 @@ namespace ts.server {
}
}
getProjectErrors() {
/**
* Get the errors that dont have any file name associated
*/
getGlobalProjectErrors() {
return filter(this.projectErrors, diagnostic => !diagnostic.file);
}
getAllProjectErrors() {
return this.projectErrors;
}
@@ -297,9 +305,19 @@ namespace ts.server {
this.rootFiles = undefined;
this.rootFilesMap = undefined;
this.program = undefined;
this.builder = undefined;
this.cachedUnresolvedImportsPerFile = undefined;
this.projectErrors = undefined;
this.lsHost.dispose();
this.lsHost = undefined;
// Clean up file watchers waiting for missing files
this.missingFilesMap.forEach(fileWatcher => fileWatcher.close());
this.missingFilesMap = undefined;
// signal language service to release source files acquired from document registry
this.languageService.dispose();
this.languageService = undefined;
}
getCompilerOptions() {
@@ -357,7 +375,7 @@ namespace ts.server {
return this.getLanguageService().getEmitOutput(info.fileName, emitOnlyDtsFiles);
}
getFileNames(excludeFilesFromExternalLibraries?: boolean) {
getFileNames(excludeFilesFromExternalLibraries?: boolean, excludeConfigFiles?: boolean) {
if (!this.program) {
return [];
}
@@ -380,9 +398,39 @@ namespace ts.server {
}
result.push(asNormalizedPath(f.fileName));
}
if (!excludeConfigFiles) {
const configFile = this.program.getCompilerOptions().configFile;
if (configFile) {
result.push(asNormalizedPath(configFile.fileName));
if (configFile.extendedSourceFiles) {
for (const f of configFile.extendedSourceFiles) {
result.push(asNormalizedPath(f));
}
}
}
}
return result;
}
hasConfigFile(configFilePath: NormalizedPath) {
if (this.program && this.languageServiceEnabled) {
const configFile = this.program.getCompilerOptions().configFile;
if (configFile) {
if (configFilePath === asNormalizedPath(configFile.fileName)) {
return true;
}
if (configFile.extendedSourceFiles) {
for (const f of configFile.extendedSourceFiles) {
if (configFilePath === asNormalizedPath(f)) {
return true;
}
}
}
}
}
return false;
}
getAllEmittableFiles() {
if (!this.languageServiceEnabled) {
return [];
@@ -410,7 +458,7 @@ namespace ts.server {
}
isRoot(info: ScriptInfo) {
return this.rootFilesMap && this.rootFilesMap.contains(info.path);
return this.rootFilesMap && this.rootFilesMap.has(info.path);
}
// add a root file to project
@@ -439,7 +487,7 @@ namespace ts.server {
}
registerFileUpdate(fileName: string) {
(this.updatedFileNames || (this.updatedFileNames = createMap<string>())).set(fileName, fileName);
(this.updatedFileNames || (this.updatedFileNames = createMap<true>())).set(fileName, true);
}
markAsDirty() {
@@ -506,7 +554,7 @@ namespace ts.server {
for (const sourceFile of this.program.getSourceFiles()) {
this.extractUnresolvedImportsFromSourceFile(sourceFile, result);
}
this.lastCachedUnresolvedImportsList = toSortedReadonlyArray(result);
this.lastCachedUnresolvedImportsList = toSortedArray(result);
}
unresolvedImports = this.lastCachedUnresolvedImportsList;
@@ -543,12 +591,12 @@ namespace ts.server {
const oldProgram = this.program;
this.program = this.languageService.getProgram();
let hasChanges = false;
// bump up the version if
// - oldProgram is not set - this is a first time updateGraph is called
// - newProgram is different from the old program and structure of the old program was not reused.
if (!oldProgram || (this.program !== oldProgram && !(oldProgram.structureIsReused & StructureIsReused.Completely))) {
hasChanges = true;
const hasChanges = !oldProgram || (this.program !== oldProgram && !(oldProgram.structureIsReused & StructureIsReused.Completely));
if (hasChanges) {
if (oldProgram) {
for (const f of oldProgram.getSourceFiles()) {
if (this.program.getSourceFileByPath(f.path)) {
@@ -561,6 +609,35 @@ namespace ts.server {
}
}
}
const missingFilePaths = this.program.getMissingFilePaths();
const missingFilePathsSet = arrayToSet(missingFilePaths);
// Files that are no longer missing (e.g. because they are no longer required)
// should no longer be watched.
this.missingFilesMap.forEach((fileWatcher, missingFilePath) => {
if (!missingFilePathsSet.has(missingFilePath)) {
this.missingFilesMap.delete(missingFilePath);
fileWatcher.close();
}
});
// Missing files that are not yet watched should be added to the map.
for (const missingFilePath of missingFilePaths) {
if (!this.missingFilesMap.has(missingFilePath)) {
const fileWatcher = this.projectService.host.watchFile(missingFilePath, (_filename: string, eventKind: FileWatcherEventKind) => {
if (eventKind === FileWatcherEventKind.Created && this.missingFilesMap.has(missingFilePath)) {
fileWatcher.close();
this.missingFilesMap.delete(missingFilePath);
// When a missing file is created, we should update the graph.
this.markAsDirty();
this.updateGraph();
}
});
this.missingFilesMap.set(missingFilePath, fileWatcher);
}
}
}
const oldExternalFiles = this.externalFiles || emptyArray as SortedReadonlyArray<string>;
@@ -583,6 +660,10 @@ namespace ts.server {
return hasChanges;
}
isWatchedMissingFile(path: Path) {
return this.missingFilesMap.has(path);
}
getScriptInfoLSHost(fileName: string) {
const scriptInfo = this.projectService.getOrCreateScriptInfo(fileName, /*openedByClient*/ false);
if (scriptInfo) {
@@ -609,7 +690,7 @@ namespace ts.server {
}
let strBuilder = "";
for (const file of this.program.getSourceFiles()) {
strBuilder += `${file.fileName}\n`;
strBuilder += `\t${file.fileName}\n`;
}
return strBuilder;
}
@@ -657,11 +738,11 @@ namespace ts.server {
if (this.lastReportedFileNames && lastKnownVersion === this.lastReportedVersion) {
// if current structure version is the same - return info without any changes
if (this.projectStructureVersion === this.lastReportedVersion && !updatedFileNames) {
return { info, projectErrors: this.projectErrors };
return { info, projectErrors: this.getGlobalProjectErrors() };
}
// compute and return the difference
const lastReportedFileNames = this.lastReportedFileNames;
const currentFiles = arrayToMap(this.getFileNames(), x => x);
const currentFiles = arrayToSet(this.getFileNames());
const added: string[] = [];
const removed: string[] = [];
@@ -679,14 +760,14 @@ namespace ts.server {
});
this.lastReportedFileNames = currentFiles;
this.lastReportedVersion = this.projectStructureVersion;
return { info, changes: { added, removed, updated }, projectErrors: this.projectErrors };
return { info, changes: { added, removed, updated }, projectErrors: this.getGlobalProjectErrors() };
}
else {
// unknown version - return everything
const projectFileNames = this.getFileNames();
this.lastReportedFileNames = arrayToMap(projectFileNames, x => x);
this.lastReportedFileNames = arrayToSet(projectFileNames);
this.lastReportedVersion = this.projectStructureVersion;
return { info, files: projectFileNames, projectErrors: this.projectErrors };
return { info, files: projectFileNames, projectErrors: this.getGlobalProjectErrors() };
}
}
@@ -702,7 +783,7 @@ namespace ts.server {
// We need to use a set here since the code can contain the same import twice,
// but that will only be one dependency.
// To avoid invernal conversion, the key of the referencedFiles map must be of type Path
const referencedFiles = createMap<boolean>();
const referencedFiles = createMap<true>();
if (sourceFile.imports && sourceFile.imports.length > 0) {
const checker: TypeChecker = this.program.getTypeChecker();
for (const importName of sourceFile.imports) {
@@ -746,7 +827,7 @@ namespace ts.server {
// remove a root file from project
protected removeRoot(info: ScriptInfo): void {
orderedRemoveItem(this.rootFiles, info);
this.rootFilesMap.remove(info.path);
this.rootFilesMap.delete(info.path);
}
}
@@ -756,7 +837,7 @@ namespace ts.server {
*/
export class InferredProject extends Project {
private static newName = (() => {
private static readonly newName = (() => {
let nextId = 1;
return () => {
const id = nextId;
@@ -776,7 +857,7 @@ namespace ts.server {
setCompilerOptions(options?: CompilerOptions) {
// Avoid manipulating the given options directly
const newOptions = options ? clone(options) : this.getCompilerOptions();
const newOptions = options ? cloneCompilerOptions(options) : this.getCompilerOptions();
if (!newOptions) {
return;
}
@@ -794,7 +875,7 @@ namespace ts.server {
// Used to keep track of what directories are watched for this project
directoriesWatchedForTsconfig: string[] = [];
constructor(projectService: ProjectService, documentRegistry: ts.DocumentRegistry, compilerOptions: CompilerOptions) {
constructor(projectService: ProjectService, documentRegistry: DocumentRegistry, compilerOptions: CompilerOptions) {
super(InferredProject.newName(),
ProjectKind.Inferred,
projectService,
@@ -855,9 +936,9 @@ namespace ts.server {
export class ConfiguredProject extends Project {
private typeAcquisition: TypeAcquisition;
private projectFileWatcher: FileWatcher;
private directoryWatcher: FileWatcher;
private directoriesWatchedForWildcards: Map<FileWatcher>;
private typeRootsWatchers: FileWatcher[];
private directoryWatcher: FileWatcher | undefined;
private directoriesWatchedForWildcards: Map<FileWatcher> | undefined;
private typeRootsWatchers: FileWatcher[] | undefined;
readonly canonicalConfigFilePath: NormalizedPath;
private plugins: PluginModule[] = [];
@@ -867,7 +948,7 @@ namespace ts.server {
constructor(configFileName: NormalizedPath,
projectService: ProjectService,
documentRegistry: ts.DocumentRegistry,
documentRegistry: DocumentRegistry,
hasExplicitListOfFiles: boolean,
compilerOptions: CompilerOptions,
private wildcardDirectories: Map<WatchDirectoryFlags>,
@@ -976,7 +1057,7 @@ namespace ts.server {
}
getExternalFiles(): SortedReadonlyArray<string> {
return toSortedReadonlyArray(flatMap(this.plugins, plugin => {
return toSortedArray(flatMap(this.plugins, plugin => {
if (typeof plugin.getExternalFiles !== "function") return;
try {
return plugin.getExternalFiles(this);
@@ -1043,6 +1124,7 @@ namespace ts.server {
if (this.projectFileWatcher) {
this.projectFileWatcher.close();
this.projectFileWatcher = undefined;
}
if (this.typeRootsWatchers) {
@@ -1052,10 +1134,12 @@ namespace ts.server {
this.typeRootsWatchers = undefined;
}
this.directoriesWatchedForWildcards.forEach(watcher => {
watcher.close();
});
this.directoriesWatchedForWildcards = undefined;
if (this.directoriesWatchedForWildcards) {
this.directoriesWatchedForWildcards.forEach(watcher => {
watcher.close();
});
this.directoriesWatchedForWildcards = undefined;
}
this.stopWatchingDirectory();
}
@@ -1070,7 +1154,7 @@ namespace ts.server {
}
getEffectiveTypeRoots() {
return ts.getEffectiveTypeRoots(this.getCompilerOptions(), this.projectService.host) || [];
return getEffectiveTypeRoots(this.getCompilerOptions(), this.projectService.host) || [];
}
}
@@ -1082,7 +1166,7 @@ namespace ts.server {
private typeAcquisition: TypeAcquisition;
constructor(public externalProjectName: string,
projectService: ProjectService,
documentRegistry: ts.DocumentRegistry,
documentRegistry: DocumentRegistry,
compilerOptions: CompilerOptions,
languageServiceEnabled: boolean,
public compileOnSaveEnabled: boolean,
@@ -1132,4 +1216,4 @@ namespace ts.server {
this.typeAcquisition = newTypeAcquisition;
}
}
}
}
+88 -32
View File
@@ -99,8 +99,11 @@ namespace ts.server.protocol {
GetSupportedCodeFixes = "getSupportedCodeFixes",
GetApplicableRefactors = "getApplicableRefactors",
GetRefactorCodeActions = "getRefactorCodeActions",
GetRefactorCodeActionsFull = "getRefactorCodeActions-full",
GetEditsForRefactor = "getEditsForRefactor",
/* @internal */
GetEditsForRefactorFull = "getEditsForRefactor-full",
// NOTE: If updating this, be sure to also update `allCommandNames` in `harness/unittests/session.ts`.
}
/**
@@ -417,52 +420,98 @@ namespace ts.server.protocol {
export type FileLocationOrRangeRequestArgs = FileLocationRequestArgs | FileRangeRequestArgs;
/**
* Request refactorings at a given position or selection area.
*/
export interface GetApplicableRefactorsRequest extends Request {
command: CommandTypes.GetApplicableRefactors;
arguments: GetApplicableRefactorsRequestArgs;
}
export type GetApplicableRefactorsRequestArgs = FileLocationOrRangeRequestArgs;
export interface ApplicableRefactorInfo {
name: string;
description: string;
}
/**
* Response is a list of available refactorings.
* Each refactoring exposes one or more "Actions"; a user selects one action to invoke a refactoring
*/
export interface GetApplicableRefactorsResponse extends Response {
body?: ApplicableRefactorInfo[];
}
export interface GetRefactorCodeActionsRequest extends Request {
command: CommandTypes.GetRefactorCodeActions;
arguments: GetRefactorCodeActionsRequestArgs;
/**
* A set of one or more available refactoring actions, grouped under a parent refactoring.
*/
export interface ApplicableRefactorInfo {
/**
* The programmatic name of the refactoring
*/
name: string;
/**
* A description of this refactoring category to show to the user.
* If the refactoring gets inlined (see below), this text will not be visible.
*/
description: string;
/**
* Inlineable refactorings can have their actions hoisted out to the top level
* of a context menu. Non-inlineanable refactorings should always be shown inside
* their parent grouping.
*
* If not specified, this value is assumed to be 'true'
*/
inlineable?: boolean;
actions: RefactorActionInfo[];
}
export type GetRefactorCodeActionsRequestArgs = FileLocationOrRangeRequestArgs & {
/* The kind of the applicable refactor */
refactorName: string;
/**
* Represents a single refactoring action - for example, the "Extract Method..." refactor might
* offer several actions, each corresponding to a surround class or closure to extract into.
*/
export type RefactorActionInfo = {
/**
* The programmatic name of the refactoring action
*/
name: string;
/**
* A description of this refactoring action to show to the user.
* If the parent refactoring is inlined away, this will be the only text shown,
* so this description should make sense by itself if the parent is inlineable=true
*/
description: string;
};
export type RefactorCodeActions = {
actions: protocol.CodeAction[];
renameLocation?: number
};
/* @internal */
export type RefactorCodeActionsFull = {
actions: ts.CodeAction[];
renameLocation?: number
};
export interface GetRefactorCodeActionsResponse extends Response {
body: RefactorCodeActions;
export interface GetEditsForRefactorRequest extends Request {
command: CommandTypes.GetEditsForRefactor;
arguments: GetEditsForRefactorRequestArgs;
}
/* @internal */
export interface GetRefactorCodeActionsFullResponse extends Response {
body: RefactorCodeActionsFull;
/**
* Request the edits that a particular refactoring action produces.
* Callers must specify the name of the refactor and the name of the action.
*/
export type GetEditsForRefactorRequestArgs = FileLocationOrRangeRequestArgs & {
/* The 'name' property from the refactoring that offered this action */
refactor: string;
/* The 'name' property from the refactoring action */
action: string;
};
export interface GetEditsForRefactorResponse extends Response {
body?: RefactorEditInfo;
}
export type RefactorEditInfo = {
edits: FileCodeEdits[];
/**
* An optional location where the editor should start a rename operation once
* the refactoring edits have been applied
*/
renameLocation?: Location;
renameFilename?: string;
};
/**
* Request for the available codefixes at a specific position.
*/
@@ -607,7 +656,7 @@ namespace ts.server.protocol {
}
/**
* Location in source code expressed as (one-based) line and character offset.
* Location in source code expressed as (one-based) line and (one-based) column offset.
*/
export interface Location {
line: number;
@@ -1910,6 +1959,13 @@ namespace ts.server.protocol {
source?: string;
}
export interface DiagnosticWithFileName extends Diagnostic {
/**
* Name of the file the diagnostic is in
*/
fileName: string;
}
export interface DiagnosticEventBody {
/**
* The file for which diagnostic information is reported.
@@ -1944,7 +2000,7 @@ namespace ts.server.protocol {
/**
* An arry of diagnostic information items for the found config file.
*/
diagnostics: Diagnostic[];
diagnostics: DiagnosticWithFileName[];
}
/**
+15 -34
View File
@@ -61,7 +61,7 @@ namespace ts.server {
: ScriptSnapshot.fromString(this.getOrLoadText());
}
public getLineInfo(line: number) {
public getLineInfo(line: number): AbsolutePositionAndLineText {
return this.switchToScriptVersionCache().getSnapshot().index.lineNumberToInfo(line);
}
/**
@@ -72,19 +72,12 @@ namespace ts.server {
const lineMap = this.getLineMap();
const start = lineMap[line]; // -1 since line is 1-based
const end = line + 1 < lineMap.length ? lineMap[line + 1] : this.text.length;
return ts.createTextSpanFromBounds(start, end);
return createTextSpanFromBounds(start, end);
}
const index = this.svc.getSnapshot().index;
const lineInfo = index.lineNumberToInfo(line + 1);
let len: number;
if (lineInfo.leaf) {
len = lineInfo.leaf.text.length;
}
else {
const nextLineInfo = index.lineNumberToInfo(line + 2);
len = nextLineInfo.offset - lineInfo.offset;
}
return ts.createTextSpan(lineInfo.offset, len);
const { lineText, absolutePosition } = index.lineNumberToInfo(line + 1);
const len = lineText !== undefined ? lineText.length : index.absolutePositionOfStartOfLine(line + 2) - absolutePosition;
return createTextSpan(absolutePosition, len);
}
/**
@@ -93,27 +86,19 @@ namespace ts.server {
*/
lineOffsetToPosition(line: number, offset: number): number {
if (!this.svc) {
return computePositionOfLineAndCharacter(this.getLineMap(), line - 1, offset - 1);
return computePositionOfLineAndCharacter(this.getLineMap(), line - 1, offset - 1, this.text);
}
const index = this.svc.getSnapshot().index;
const lineInfo = index.lineNumberToInfo(line);
// TODO: assert this offset is actually on the line
return (lineInfo.offset + offset - 1);
return this.svc.getSnapshot().index.absolutePositionOfStartOfLine(line) + (offset - 1);
}
/**
* @param line 1-based index
* @param offset 1-based index
*/
positionToLineOffset(position: number): ILineInfo {
positionToLineOffset(position: number): protocol.Location {
if (!this.svc) {
const { line, character } = computeLineAndCharacterOfPosition(this.getLineMap(), position);
return { line: line + 1, offset: character + 1 };
}
const index = this.svc.getSnapshot().index;
const lineOffset = index.charOffsetToLineNumberAndPos(position);
return { line: lineOffset.line, offset: lineOffset.offset + 1 };
return this.svc.getSnapshot().index.positionToLineOffset(position);
}
private getFileText(tempFileName?: string) {
@@ -126,7 +111,7 @@ namespace ts.server {
private switchToScriptVersionCache(newText?: string): ScriptVersionCache {
if (!this.svc) {
this.svc = ScriptVersionCache.fromString(this.host, newText !== undefined ? newText : this.getOrLoadText());
this.svc = ScriptVersionCache.fromString(newText !== undefined ? newText : this.getOrLoadText());
this.svcVersion++;
this.text = undefined;
}
@@ -162,7 +147,7 @@ namespace ts.server {
* All projects that include this file
*/
readonly containingProjects: Project[] = [];
private formatCodeSettings: ts.FormatCodeSettings;
private formatCodeSettings: FormatCodeSettings;
readonly path: Path;
private fileWatcher: FileWatcher;
@@ -247,7 +232,7 @@ namespace ts.server {
}
break;
default:
removeItemFromSet(this.containingProjects, project);
unorderedRemoveItem(this.containingProjects, project);
break;
}
}
@@ -257,7 +242,7 @@ namespace ts.server {
// detach is unnecessary since we'll clean the list of containing projects anyways
p.removeFile(this, /*detachFromProjects*/ false);
}
this.containingProjects.length = 0;
clear(this.containingProjects);
}
getDefaultProject() {
@@ -334,7 +319,7 @@ namespace ts.server {
}
}
getLineInfo(line: number) {
getLineInfo(line: number): AbsolutePositionAndLineText {
return this.textStorage.getLineInfo(line);
}
@@ -364,11 +349,7 @@ namespace ts.server {
return this.textStorage.lineOffsetToPosition(line, offset);
}
/**
* @param line 1-based index
* @param offset 1-based index
*/
positionToLineOffset(position: number): ILineInfo {
positionToLineOffset(position: number): protocol.Location {
return this.textStorage.positionToLineOffset(position);
}
+168 -249
View File
@@ -8,15 +8,13 @@ namespace ts.server {
export interface LineCollection {
charCount(): number;
lineCount(): number;
isLeaf(): boolean;
isLeaf(): this is LineLeaf;
walk(rangeStart: number, rangeLength: number, walkFns: ILineIndexWalker): void;
}
export interface ILineInfo {
line: number;
offset: number;
text?: string;
leaf?: LineLeaf;
export interface AbsolutePositionAndLineText {
absolutePosition: number;
lineText: string | undefined;
}
export enum CharRangeSection {
@@ -33,41 +31,35 @@ namespace ts.server {
done: boolean;
leaf(relativeStart: number, relativeLength: number, lineCollection: LineLeaf): void;
pre?(relativeStart: number, relativeLength: number, lineCollection: LineCollection,
parent: LineNode, nodeType: CharRangeSection): LineCollection;
parent: LineNode, nodeType: CharRangeSection): void;
post?(relativeStart: number, relativeLength: number, lineCollection: LineCollection,
parent: LineNode, nodeType: CharRangeSection): LineCollection;
parent: LineNode, nodeType: CharRangeSection): void;
}
class BaseLineIndexWalker implements ILineIndexWalker {
class EditWalker implements ILineIndexWalker {
goSubtree = true;
done = false;
leaf(_rangeStart: number, _rangeLength: number, _ll: LineLeaf) {
}
}
get done() { return false; }
class EditWalker extends BaseLineIndexWalker {
lineIndex = new LineIndex();
readonly lineIndex = new LineIndex();
// path to start of range
startPath: LineCollection[];
endBranch: LineCollection[] = [];
branchNode: LineNode;
private readonly startPath: LineCollection[];
private readonly endBranch: LineCollection[] = [];
private branchNode: LineNode;
// path to current node
stack: LineNode[];
state = CharRangeSection.Entire;
lineCollectionAtBranch: LineCollection;
initialText = "";
trailingText = "";
suppressTrailingText = false;
private readonly stack: LineNode[];
private state = CharRangeSection.Entire;
private lineCollectionAtBranch: LineCollection;
private initialText = "";
private trailingText = "";
constructor() {
super();
this.lineIndex.root = new LineNode();
this.startPath = [this.lineIndex.root];
this.stack = [this.lineIndex.root];
}
insertLines(insertedText: string) {
if (this.suppressTrailingText) {
insertLines(insertedText: string, suppressTrailingText: boolean) {
if (suppressTrailingText) {
this.trailingText = "";
}
if (insertedText) {
@@ -80,7 +72,7 @@ namespace ts.server {
const lines = lm.lines;
if (lines.length > 1) {
if (lines[lines.length - 1] === "") {
lines.length--;
lines.pop();
}
}
let branchParent: LineNode;
@@ -103,22 +95,20 @@ namespace ts.server {
}
// path at least length two (root and leaf)
let insertionNode = <LineNode>this.startPath[this.startPath.length - 2];
const leafNode = <LineLeaf>this.startPath[this.startPath.length - 1];
const len = lines.length;
if (len > 0) {
if (lines.length > 0) {
leafNode.text = lines[0];
if (len > 1) {
let insertedNodes = <LineCollection[]>new Array(len - 1);
if (lines.length > 1) {
let insertedNodes = <LineCollection[]>new Array(lines.length - 1);
let startNode = <LineCollection>leafNode;
for (let i = 1; i < lines.length; i++) {
insertedNodes[i - 1] = new LineLeaf(lines[i]);
}
let pathIndex = this.startPath.length - 2;
while (pathIndex >= 0) {
insertionNode = <LineNode>this.startPath[pathIndex];
const insertionNode = <LineNode>this.startPath[pathIndex];
insertedNodes = insertionNode.insertAt(startNode, insertedNodes);
pathIndex--;
startNode = insertionNode;
@@ -140,6 +130,7 @@ namespace ts.server {
}
}
else {
const insertionNode = <LineNode>this.startPath[this.startPath.length - 2];
// no content for leaf node, so delete it
insertionNode.remove(leafNode);
for (let j = this.startPath.length - 2; j >= 0; j--) {
@@ -150,18 +141,17 @@ namespace ts.server {
return this.lineIndex;
}
post(_relativeStart: number, _relativeLength: number, lineCollection: LineCollection): LineCollection {
post(_relativeStart: number, _relativeLength: number, lineCollection: LineCollection): void {
// have visited the path for start of range, now looking for end
// if range is on single line, we will never make this state transition
if (lineCollection === this.lineCollectionAtBranch) {
this.state = CharRangeSection.End;
}
// always pop stack because post only called when child has been visited
this.stack.length--;
return undefined;
this.stack.pop();
}
pre(_relativeStart: number, _relativeLength: number, lineCollection: LineCollection, _parent: LineCollection, nodeType: CharRangeSection) {
pre(_relativeStart: number, _relativeLength: number, lineCollection: LineCollection, _parent: LineCollection, nodeType: CharRangeSection): void {
// currentNode corresponds to parent, but in the new tree
const currentNode = this.stack[this.stack.length - 1];
@@ -193,20 +183,20 @@ namespace ts.server {
else {
child = fresh(lineCollection);
currentNode.add(child);
this.startPath[this.startPath.length] = child;
this.startPath.push(child);
}
break;
case CharRangeSection.Entire:
if (this.state !== CharRangeSection.End) {
child = fresh(lineCollection);
currentNode.add(child);
this.startPath[this.startPath.length] = child;
this.startPath.push(child);
}
else {
if (!lineCollection.isLeaf()) {
child = fresh(lineCollection);
currentNode.add(child);
this.endBranch[this.endBranch.length] = child;
this.endBranch.push(child);
}
}
break;
@@ -221,7 +211,7 @@ namespace ts.server {
if (!lineCollection.isLeaf()) {
child = fresh(lineCollection);
currentNode.add(child);
this.endBranch[this.endBranch.length] = child;
this.endBranch.push(child);
}
}
break;
@@ -233,9 +223,8 @@ namespace ts.server {
break;
}
if (this.goSubtree) {
this.stack[this.stack.length] = <LineNode>child;
this.stack.push(<LineNode>child);
}
return lineCollection;
}
// just gather text from the leaves
leaf(relativeStart: number, relativeLength: number, ll: LineLeaf) {
@@ -259,22 +248,21 @@ namespace ts.server {
}
getTextChangeRange() {
return ts.createTextChangeRange(ts.createTextSpan(this.pos, this.deleteLen),
return createTextChangeRange(createTextSpan(this.pos, this.deleteLen),
this.insertedText ? this.insertedText.length : 0);
}
}
export class ScriptVersionCache {
changes: TextChange[] = [];
versions: LineIndexSnapshot[] = new Array<LineIndexSnapshot>(ScriptVersionCache.maxVersions);
minVersion = 0; // no versions earlier than min version will maintain change history
private changes: TextChange[] = [];
private readonly versions: LineIndexSnapshot[] = new Array<LineIndexSnapshot>(ScriptVersionCache.maxVersions);
private minVersion = 0; // no versions earlier than min version will maintain change history
private host: ServerHost;
private currentVersion = 0;
static changeNumberThreshold = 8;
static changeLengthThreshold = 256;
static maxVersions = 8;
private static readonly changeNumberThreshold = 8;
private static readonly changeLengthThreshold = 256;
private static readonly maxVersions = 8;
private versionToIndex(version: number) {
if (version < this.minVersion || version > this.currentVersion) {
@@ -289,10 +277,10 @@ namespace ts.server {
// REVIEW: can optimize by coalescing simple edits
edit(pos: number, deleteLen: number, insertedText?: string) {
this.changes[this.changes.length] = new TextChange(pos, deleteLen, insertedText);
if ((this.changes.length > ScriptVersionCache.changeNumberThreshold) ||
(deleteLen > ScriptVersionCache.changeLengthThreshold) ||
(insertedText && (insertedText.length > ScriptVersionCache.changeLengthThreshold))) {
this.changes.push(new TextChange(pos, deleteLen, insertedText));
if (this.changes.length > ScriptVersionCache.changeNumberThreshold ||
deleteLen > ScriptVersionCache.changeLengthThreshold ||
insertedText && insertedText.length > ScriptVersionCache.changeLengthThreshold) {
this.getSnapshot();
}
}
@@ -308,21 +296,11 @@ namespace ts.server {
return this.currentVersion;
}
reloadFromFile(filename: string) {
let content = this.host.readFile(filename);
// If the file doesn't exist or cannot be read, we should
// wipe out its cached content on the server to avoid side effects.
if (!content) {
content = "";
}
this.reload(content);
}
// reload whole script, leaving no change history behind reload
reload(script: string) {
this.currentVersion++;
this.changes = []; // history wiped out by reload
const snap = new LineIndexSnapshot(this.currentVersion, this);
const snap = new LineIndexSnapshot(this.currentVersion, this, new LineIndex());
// delete all versions
for (let i = 0; i < this.versions.length; i++) {
@@ -330,7 +308,6 @@ namespace ts.server {
}
this.versions[this.currentVersionToIndex()] = snap;
snap.index = new LineIndex();
const lm = LineIndex.linesFromText(script);
snap.index.load(lm.lines);
@@ -344,9 +321,7 @@ namespace ts.server {
for (const change of this.changes) {
snapIndex = snapIndex.edit(change.pos, change.deleteLen, change.insertedText);
}
snap = new LineIndexSnapshot(this.currentVersion + 1, this);
snap.index = snapIndex;
snap.changesSincePreviousVersion = this.changes;
snap = new LineIndexSnapshot(this.currentVersion + 1, this, snapIndex, this.changes);
this.currentVersion = snap.version;
this.versions[this.currentVersionToIndex()] = snap;
@@ -362,41 +337,36 @@ namespace ts.server {
getTextChangesBetweenVersions(oldVersion: number, newVersion: number) {
if (oldVersion < newVersion) {
if (oldVersion >= this.minVersion) {
const textChangeRanges: ts.TextChangeRange[] = [];
const textChangeRanges: TextChangeRange[] = [];
for (let i = oldVersion + 1; i <= newVersion; i++) {
const snap = this.versions[this.versionToIndex(i)];
for (const textChange of snap.changesSincePreviousVersion) {
textChangeRanges[textChangeRanges.length] = textChange.getTextChangeRange();
textChangeRanges.push(textChange.getTextChangeRange());
}
}
return ts.collapseTextChangeRangesAcrossMultipleVersions(textChangeRanges);
return collapseTextChangeRangesAcrossMultipleVersions(textChangeRanges);
}
else {
return undefined;
}
}
else {
return ts.unchangedTextChangeRange;
return unchangedTextChangeRange;
}
}
static fromString(host: ServerHost, script: string) {
static fromString(script: string) {
const svc = new ScriptVersionCache();
const snap = new LineIndexSnapshot(0, svc);
const snap = new LineIndexSnapshot(0, svc, new LineIndex());
svc.versions[svc.currentVersion] = snap;
svc.host = host;
snap.index = new LineIndex();
const lm = LineIndex.linesFromText(script);
snap.index.load(lm.lines);
return svc;
}
}
export class LineIndexSnapshot implements ts.IScriptSnapshot {
index: LineIndex;
changesSincePreviousVersion: TextChange[] = [];
constructor(readonly version: number, readonly cache: ScriptVersionCache) {
export class LineIndexSnapshot implements IScriptSnapshot {
constructor(readonly version: number, readonly cache: ScriptVersionCache, readonly index: LineIndex, readonly changesSincePreviousVersion: ReadonlyArray<TextChange> = emptyArray) {
}
getText(rangeStart: number, rangeEnd: number) {
@@ -407,37 +377,14 @@ namespace ts.server {
return this.index.root.charCount();
}
// this requires linear space so don't hold on to these
getLineStartPositions(): number[] {
const starts: number[] = [-1];
let count = 1;
let pos = 0;
this.index.every(ll => {
starts[count] = pos;
count++;
pos += ll.text.length;
return true;
}, 0);
return starts;
}
getLineMapper() {
return (line: number) => {
return this.index.lineNumberToInfo(line).offset;
};
}
getTextChangeRangeSinceVersion(scriptVersion: number) {
if (this.version <= scriptVersion) {
return ts.unchangedTextChangeRange;
}
else {
return this.cache.getTextChangesBetweenVersions(scriptVersion, this.version);
}
}
getChangeRange(oldSnapshot: ts.IScriptSnapshot): ts.TextChangeRange {
getChangeRange(oldSnapshot: IScriptSnapshot): TextChangeRange {
if (oldSnapshot instanceof LineIndexSnapshot && this.cache === oldSnapshot.cache) {
return this.getTextChangeRangeSinceVersion(oldSnapshot.version);
if (this.version <= oldSnapshot.version) {
return unchangedTextChangeRange;
}
else {
return this.cache.getTextChangesBetweenVersions(oldSnapshot.version, this.version);
}
}
}
}
@@ -447,22 +394,27 @@ namespace ts.server {
// set this to true to check each edit for accuracy
checkEdits = false;
charOffsetToLineNumberAndPos(charOffset: number) {
return this.root.charOffsetToLineNumberAndPos(1, charOffset);
absolutePositionOfStartOfLine(oneBasedLine: number): number {
return this.lineNumberToInfo(oneBasedLine).absolutePosition;
}
lineNumberToInfo(lineNumber: number): ILineInfo {
positionToLineOffset(position: number): protocol.Location {
const { oneBasedLine, zeroBasedColumn } = this.root.charOffsetToLineInfo(1, position);
return { line: oneBasedLine, offset: zeroBasedColumn + 1 };
}
private positionToColumnAndLineText(position: number): { zeroBasedColumn: number, lineText: string } {
return this.root.charOffsetToLineInfo(1, position);
}
lineNumberToInfo(oneBasedLine: number): AbsolutePositionAndLineText {
const lineCount = this.root.lineCount();
if (lineNumber <= lineCount) {
const lineInfo = this.root.lineNumberToInfo(lineNumber, 0);
lineInfo.line = lineNumber;
return lineInfo;
if (oneBasedLine <= lineCount) {
const { position, leaf } = this.root.lineNumberToInfo(oneBasedLine, 0);
return { absolutePosition: position, lineText: leaf && leaf.text };
}
else {
return {
line: lineNumber,
offset: this.root.charCount()
};
return { absolutePosition: this.root.charCount(), lineText: undefined };
}
}
@@ -508,7 +460,7 @@ namespace ts.server {
const walkFns = {
goSubtree: true,
done: false,
leaf: function (this: ILineIndexWalker, relativeStart: number, relativeLength: number, ll: LineLeaf) {
leaf(this: ILineIndexWalker, relativeStart: number, relativeLength: number, ll: LineLeaf) {
if (!f(ll, relativeStart, relativeLength)) {
this.done = true;
}
@@ -518,12 +470,9 @@ namespace ts.server {
return !walkFns.done;
}
edit(pos: number, deleteLength: number, newText?: string) {
function editFlat(source: string, s: number, dl: number, nt = "") {
return source.substring(0, s) + nt + source.substring(s + dl, source.length);
}
edit(pos: number, deleteLength: number, newText?: string): LineIndex {
if (this.root.charCount() === 0) {
// TODO: assert deleteLength === 0
Debug.assert(deleteLength === 0); // Can't delete from empty document
if (newText !== undefined) {
this.load(LineIndex.linesFromText(newText).lines);
return this;
@@ -532,9 +481,11 @@ namespace ts.server {
else {
let checkText: string;
if (this.checkEdits) {
checkText = editFlat(this.getText(0, this.root.charCount()), pos, deleteLength, newText);
const source = this.getText(0, this.root.charCount());
checkText = source.slice(0, pos) + newText + source.slice(pos + deleteLength);
}
const walker = new EditWalker();
let suppressTrailingText = false;
if (pos >= this.root.charCount()) {
// insert at end
pos = this.root.charCount() - 1;
@@ -546,93 +497,77 @@ namespace ts.server {
newText = endString;
}
deleteLength = 0;
walker.suppressTrailingText = true;
suppressTrailingText = true;
}
else if (deleteLength > 0) {
// check whether last characters deleted are line break
const e = pos + deleteLength;
const lineInfo = this.charOffsetToLineNumberAndPos(e);
if ((lineInfo && (lineInfo.offset === 0))) {
const { zeroBasedColumn, lineText } = this.positionToColumnAndLineText(e);
if (zeroBasedColumn === 0) {
// move range end just past line that will merge with previous line
deleteLength += lineInfo.text.length;
deleteLength += lineText.length;
// store text by appending to end of insertedText
if (newText) {
newText = newText + lineInfo.text;
}
else {
newText = lineInfo.text;
}
newText = newText ? newText + lineText : lineText;
}
}
if (pos < this.root.charCount()) {
this.root.walk(pos, deleteLength, walker);
walker.insertLines(newText);
}
this.root.walk(pos, deleteLength, walker);
walker.insertLines(newText, suppressTrailingText);
if (this.checkEdits) {
const updatedText = this.getText(0, this.root.charCount());
const updatedText = walker.lineIndex.getText(0, walker.lineIndex.getLength());
Debug.assert(checkText === updatedText, "buffer edit mismatch");
}
return walker.lineIndex;
}
}
static buildTreeFromBottom(nodes: LineCollection[]): LineNode {
const nodeCount = Math.ceil(nodes.length / lineCollectionCapacity);
const interiorNodes: LineNode[] = [];
private static buildTreeFromBottom(nodes: LineCollection[]): LineNode {
if (nodes.length < lineCollectionCapacity) {
return new LineNode(nodes);
}
const interiorNodes = new Array<LineNode>(Math.ceil(nodes.length / lineCollectionCapacity));
let nodeIndex = 0;
for (let i = 0; i < nodeCount; i++) {
interiorNodes[i] = new LineNode();
let charCount = 0;
let lineCount = 0;
for (let j = 0; j < lineCollectionCapacity; j++) {
if (nodeIndex < nodes.length) {
interiorNodes[i].add(nodes[nodeIndex]);
charCount += nodes[nodeIndex].charCount();
lineCount += nodes[nodeIndex].lineCount();
}
else {
break;
}
nodeIndex++;
}
interiorNodes[i].totalChars = charCount;
interiorNodes[i].totalLines = lineCount;
}
if (interiorNodes.length === 1) {
return interiorNodes[0];
}
else {
return this.buildTreeFromBottom(interiorNodes);
for (let i = 0; i < interiorNodes.length; i++) {
const end = Math.min(nodeIndex + lineCollectionCapacity, nodes.length);
interiorNodes[i] = new LineNode(nodes.slice(nodeIndex, end));
nodeIndex = end;
}
return this.buildTreeFromBottom(interiorNodes);
}
static linesFromText(text: string) {
const lineStarts = ts.computeLineStarts(text);
const lineMap = computeLineStarts(text);
if (lineStarts.length === 0) {
return { lines: <string[]>[], lineMap: lineStarts };
if (lineMap.length === 0) {
return { lines: <string[]>[], lineMap };
}
const lines = <string[]>new Array(lineStarts.length);
const lc = lineStarts.length - 1;
const lines = <string[]>new Array(lineMap.length);
const lc = lineMap.length - 1;
for (let lmi = 0; lmi < lc; lmi++) {
lines[lmi] = text.substring(lineStarts[lmi], lineStarts[lmi + 1]);
lines[lmi] = text.substring(lineMap[lmi], lineMap[lmi + 1]);
}
const endText = text.substring(lineStarts[lc]);
const endText = text.substring(lineMap[lc]);
if (endText.length > 0) {
lines[lc] = endText;
}
else {
lines.length--;
lines.pop();
}
return { lines: lines, lineMap: lineStarts };
return { lines, lineMap };
}
}
export class LineNode implements LineCollection {
totalChars = 0;
totalLines = 0;
children: LineCollection[] = [];
constructor(private readonly children: LineCollection[] = []) {
if (children.length) this.updateCounts();
}
isLeaf() {
return false;
@@ -647,7 +582,7 @@ namespace ts.server {
}
}
execWalk(rangeStart: number, rangeLength: number, walkFns: ILineIndexWalker, childIndex: number, nodeType: CharRangeSection) {
private execWalk(rangeStart: number, rangeLength: number, walkFns: ILineIndexWalker, childIndex: number, nodeType: CharRangeSection) {
if (walkFns.pre) {
walkFns.pre(rangeStart, rangeLength, this.children[childIndex], this, nodeType);
}
@@ -663,7 +598,7 @@ namespace ts.server {
return walkFns.done;
}
skipChild(relativeStart: number, relativeLength: number, childIndex: number, walkFns: ILineIndexWalker, nodeType: CharRangeSection) {
private skipChild(relativeStart: number, relativeLength: number, childIndex: number, walkFns: ILineIndexWalker, nodeType: CharRangeSection) {
if (walkFns.pre && (!walkFns.done)) {
walkFns.pre(relativeStart, relativeLength, this.children[childIndex], this, nodeType);
walkFns.goSubtree = true;
@@ -673,16 +608,14 @@ namespace ts.server {
walk(rangeStart: number, rangeLength: number, walkFns: ILineIndexWalker) {
// assume (rangeStart < this.totalChars) && (rangeLength <= this.totalChars)
let childIndex = 0;
let child = this.children[0];
let childCharCount = child.charCount();
let childCharCount = this.children[childIndex].charCount();
// find sub-tree containing start
let adjustedStart = rangeStart;
while (adjustedStart >= childCharCount) {
this.skipChild(adjustedStart, rangeLength, childIndex, walkFns, CharRangeSection.PreStart);
adjustedStart -= childCharCount;
childIndex++;
child = this.children[childIndex];
childCharCount = child.charCount();
childCharCount = this.children[childIndex].charCount();
}
// Case I: both start and end of range in same subtree
if ((adjustedStart + rangeLength) <= childCharCount) {
@@ -697,7 +630,7 @@ namespace ts.server {
}
let adjustedLength = rangeLength - (childCharCount - adjustedStart);
childIndex++;
child = this.children[childIndex];
const child = this.children[childIndex];
childCharCount = child.charCount();
while (adjustedLength > childCharCount) {
if (this.execWalk(0, childCharCount, walkFns, childIndex, CharRangeSection.Mid)) {
@@ -705,8 +638,7 @@ namespace ts.server {
}
adjustedLength -= childCharCount;
childIndex++;
child = this.children[childIndex];
childCharCount = child.charCount();
childCharCount = this.children[childIndex].charCount();
}
if (adjustedLength > 0) {
if (this.execWalk(0, adjustedLength, walkFns, childIndex, CharRangeSection.End)) {
@@ -725,103 +657,91 @@ namespace ts.server {
}
}
charOffsetToLineNumberAndPos(lineNumber: number, charOffset: number): ILineInfo {
const childInfo = this.childFromCharOffset(lineNumber, charOffset);
// Input position is relative to the start of this node.
// Output line number is absolute.
charOffsetToLineInfo(lineNumberAccumulator: number, relativePosition: number): { oneBasedLine: number, zeroBasedColumn: number, lineText: string | undefined } {
const childInfo = this.childFromCharOffset(lineNumberAccumulator, relativePosition);
if (!childInfo.child) {
return {
line: lineNumber,
offset: charOffset,
oneBasedLine: lineNumberAccumulator,
zeroBasedColumn: relativePosition,
lineText: undefined,
};
}
else if (childInfo.childIndex < this.children.length) {
if (childInfo.child.isLeaf()) {
return {
line: childInfo.lineNumber,
offset: childInfo.charOffset,
text: (<LineLeaf>(childInfo.child)).text,
leaf: (<LineLeaf>(childInfo.child))
oneBasedLine: childInfo.lineNumberAccumulator,
zeroBasedColumn: childInfo.relativePosition,
lineText: childInfo.child.text,
};
}
else {
const lineNode = <LineNode>(childInfo.child);
return lineNode.charOffsetToLineNumberAndPos(childInfo.lineNumber, childInfo.charOffset);
return lineNode.charOffsetToLineInfo(childInfo.lineNumberAccumulator, childInfo.relativePosition);
}
}
else {
const lineInfo = this.lineNumberToInfo(this.lineCount(), 0);
return { line: this.lineCount(), offset: lineInfo.leaf.charCount() };
return { oneBasedLine: this.lineCount(), zeroBasedColumn: lineInfo.leaf.charCount(), lineText: undefined };
}
}
lineNumberToInfo(lineNumber: number, charOffset: number): ILineInfo {
const childInfo = this.childFromLineNumber(lineNumber, charOffset);
lineNumberToInfo(relativeOneBasedLine: number, positionAccumulator: number): { position: number, leaf: LineLeaf | undefined } {
const childInfo = this.childFromLineNumber(relativeOneBasedLine, positionAccumulator);
if (!childInfo.child) {
return {
line: lineNumber,
offset: charOffset
};
return { position: positionAccumulator, leaf: undefined };
}
else if (childInfo.child.isLeaf()) {
return {
line: lineNumber,
offset: childInfo.charOffset,
text: (<LineLeaf>(childInfo.child)).text,
leaf: (<LineLeaf>(childInfo.child))
};
return { position: childInfo.positionAccumulator, leaf: childInfo.child };
}
else {
const lineNode = <LineNode>(childInfo.child);
return lineNode.lineNumberToInfo(childInfo.relativeLineNumber, childInfo.charOffset);
return lineNode.lineNumberToInfo(childInfo.relativeOneBasedLine, childInfo.positionAccumulator);
}
}
childFromLineNumber(lineNumber: number, charOffset: number) {
/**
* Input line number is relative to the start of this node.
* Output line number is relative to the child.
* positionAccumulator will be an absolute position once relativeLineNumber reaches 0.
*/
private childFromLineNumber(relativeOneBasedLine: number, positionAccumulator: number): { child: LineCollection, relativeOneBasedLine: number, positionAccumulator: number } {
let child: LineCollection;
let relativeLineNumber = lineNumber;
let i: number;
let len: number;
for (i = 0, len = this.children.length; i < len; i++) {
for (i = 0; i < this.children.length; i++) {
child = this.children[i];
const childLineCount = child.lineCount();
if (childLineCount >= relativeLineNumber) {
if (childLineCount >= relativeOneBasedLine) {
break;
}
else {
relativeLineNumber -= childLineCount;
charOffset += child.charCount();
relativeOneBasedLine -= childLineCount;
positionAccumulator += child.charCount();
}
}
return {
child: child,
childIndex: i,
relativeLineNumber: relativeLineNumber,
charOffset: charOffset
};
return { child, relativeOneBasedLine, positionAccumulator };
}
childFromCharOffset(lineNumber: number, charOffset: number) {
private childFromCharOffset(lineNumberAccumulator: number, relativePosition: number
): { child: LineCollection, childIndex: number, relativePosition: number, lineNumberAccumulator: number } {
let child: LineCollection;
let i: number;
let len: number;
for (i = 0, len = this.children.length; i < len; i++) {
child = this.children[i];
if (child.charCount() > charOffset) {
if (child.charCount() > relativePosition) {
break;
}
else {
charOffset -= child.charCount();
lineNumber += child.lineCount();
relativePosition -= child.charCount();
lineNumberAccumulator += child.lineCount();
}
}
return {
child: child,
childIndex: i,
charOffset: charOffset,
lineNumber: lineNumber
};
return { child, childIndex: i, relativePosition, lineNumberAccumulator };
}
splitAfter(childIndex: number) {
private splitAfter(childIndex: number) {
let splitNode: LineNode;
const clen = this.children.length;
childIndex++;
@@ -846,13 +766,12 @@ namespace ts.server {
this.children[i] = this.children[i + 1];
}
}
this.children.length--;
this.children.pop();
}
findChildIndex(child: LineCollection) {
let childIndex = 0;
const clen = this.children.length;
while ((this.children[childIndex] !== child) && (childIndex < clen)) childIndex++;
private findChildIndex(child: LineCollection) {
const childIndex = this.children.indexOf(child);
Debug.assert(childIndex !== -1);
return childIndex;
}
@@ -895,12 +814,12 @@ namespace ts.server {
}
for (let i = splitNodes.length - 1; i >= 0; i--) {
if (splitNodes[i].children.length === 0) {
splitNodes.length--;
splitNodes.pop();
}
}
}
if (shiftNode) {
splitNodes[splitNodes.length] = shiftNode;
splitNodes.push(shiftNode);
}
this.updateCounts();
for (let i = 0; i < splitNodeCount; i++) {
@@ -911,9 +830,9 @@ namespace ts.server {
}
// assume there is room for the item; return true if more room
add(collection: LineCollection) {
this.children[this.children.length] = collection;
return (this.children.length < lineCollectionCapacity);
add(collection: LineCollection): void {
this.children.push(collection);
Debug.assert(this.children.length <= lineCollectionCapacity);
}
charCount() {
+29 -17
View File
@@ -43,7 +43,7 @@ namespace ts.server {
process.env.USERPROFILE ||
(process.env.HOMEDRIVE && process.env.HOMEPATH && normalizeSlashes(process.env.HOMEDRIVE + process.env.HOMEPATH)) ||
os.tmpdir();
return combinePaths(normalizeSlashes(basePath), "Microsoft/TypeScript");
return combinePaths(combinePaths(normalizeSlashes(basePath), "Microsoft/TypeScript"), versionMajorMinor);
}
case "openbsd":
case "freebsd":
@@ -51,7 +51,7 @@ namespace ts.server {
case "linux":
case "android": {
const cacheLocation = getNonWindowsCacheLocation(process.platform === "darwin");
return combinePaths(cacheLocation, "typescript");
return combinePaths(combinePaths(cacheLocation, "typescript"), versionMajorMinor);
}
default:
Debug.fail(`unsupported platform '${process.platform}'`);
@@ -138,7 +138,7 @@ namespace ts.server {
terminal: false,
});
class Logger implements ts.server.Logger {
class Logger implements server.Logger {
private fd = -1;
private seq = 0;
private inGroup = false;
@@ -200,7 +200,7 @@ namespace ts.server {
msg(s: string, type: Msg.Types = Msg.Err) {
if (this.fd >= 0 || this.traceToConsole) {
s = s + "\n";
s = `[${nowString()}] ${s}\n`;
const prefix = Logger.padStringRight(type + " " + this.seq.toString(), " ");
if (this.firstInGroup) {
s = prefix + s;
@@ -222,6 +222,12 @@ namespace ts.server {
}
}
// E.g. "12:34:56.789"
function nowString() {
const d = new Date();
return `${d.getHours()}:${d.getMinutes()}:${d.getSeconds()}.${d.getMilliseconds()}`;
}
class NodeTypingsInstaller implements ITypingsInstaller {
private installer: NodeChildProcess;
private installerPidReported = false;
@@ -510,6 +516,7 @@ namespace ts.server {
const watchedFiles: WatchedFile[] = [];
let nextFileToCheck = 0;
let watchTimer: any;
return { getModifiedTime, poll, startWatchTimer, addFile, removeFile };
function getModifiedTime(fileName: string): Date {
return fs.statSync(fileName).mtime;
@@ -523,11 +530,20 @@ namespace ts.server {
fs.stat(watchedFile.fileName, (err: any, stats: any) => {
if (err) {
watchedFile.callback(watchedFile.fileName);
watchedFile.callback(watchedFile.fileName, FileWatcherEventKind.Changed);
}
else if (watchedFile.mtime.getTime() !== stats.mtime.getTime()) {
watchedFile.mtime = getModifiedTime(watchedFile.fileName);
watchedFile.callback(watchedFile.fileName, watchedFile.mtime.getTime() === 0);
else {
const oldTime = watchedFile.mtime.getTime();
const newTime = stats.mtime.getTime();
if (oldTime !== newTime) {
watchedFile.mtime = stats.mtime;
const eventKind = oldTime === 0
? FileWatcherEventKind.Created
: newTime === 0
? FileWatcherEventKind.Deleted
: FileWatcherEventKind.Changed;
watchedFile.callback(watchedFile.fileName, eventKind);
}
}
});
}
@@ -559,7 +575,9 @@ namespace ts.server {
const file: WatchedFile = {
fileName,
callback,
mtime: getModifiedTime(fileName)
mtime: sys.fileExists(fileName)
? getModifiedTime(fileName)
: new Date(0) // Any subsequent modification will occur after this time
};
watchedFiles.push(file);
@@ -572,14 +590,6 @@ namespace ts.server {
function removeFile(file: WatchedFile) {
unorderedRemoveItem(watchedFiles, file);
}
return {
getModifiedTime: getModifiedTime,
poll: poll,
startWatchTimer: startWatchTimer,
addFile: addFile,
removeFile: removeFile
};
}
// REVIEW: for now this implementation uses polling.
@@ -747,6 +757,8 @@ namespace ts.server {
validateLocaleAndSetLanguage(localeStr, sys);
}
setStackTraceLimit();
const typingSafeListLocation = findArgument(Arguments.TypingSafeListLocation);
const npmLocation = findArgument(Arguments.NpmLocation);
+201 -128
View File
@@ -49,7 +49,7 @@ namespace ts.server {
interface FileStart {
file: string;
start: ILineInfo;
start: protocol.Location;
}
function compareNumber(a: number, b: number) {
@@ -72,26 +72,32 @@ namespace ts.server {
}
}
function formatDiag(fileName: NormalizedPath, project: Project, diag: ts.Diagnostic): protocol.Diagnostic {
function formatDiag(fileName: NormalizedPath, project: Project, diag: Diagnostic): protocol.Diagnostic {
const scriptInfo = project.getScriptInfoForNormalizedPath(fileName);
return {
start: scriptInfo.positionToLineOffset(diag.start),
end: scriptInfo.positionToLineOffset(diag.start + diag.length),
text: ts.flattenDiagnosticMessageText(diag.messageText, "\n"),
text: flattenDiagnosticMessageText(diag.messageText, "\n"),
code: diag.code,
category: DiagnosticCategory[diag.category].toLowerCase(),
source: diag.source
};
}
function formatConfigFileDiag(diag: ts.Diagnostic): protocol.Diagnostic {
return {
start: undefined,
end: undefined,
text: ts.flattenDiagnosticMessageText(diag.messageText, "\n"),
category: DiagnosticCategory[diag.category].toLowerCase(),
source: diag.source
};
function convertToLocation(lineAndCharacter: LineAndCharacter): protocol.Location {
return { line: lineAndCharacter.line + 1, offset: lineAndCharacter.character + 1 };
}
function formatConfigFileDiag(diag: Diagnostic, includeFileName: true): protocol.DiagnosticWithFileName;
function formatConfigFileDiag(diag: Diagnostic, includeFileName: false): protocol.Diagnostic;
function formatConfigFileDiag(diag: Diagnostic, includeFileName: boolean): protocol.Diagnostic | protocol.DiagnosticWithFileName {
const start = diag.file && convertToLocation(getLineAndCharacterOfPosition(diag.file, diag.start));
const end = diag.file && convertToLocation(getLineAndCharacterOfPosition(diag.file, diag.start + diag.length));
const text = flattenDiagnosticMessageText(diag.messageText, "\n");
const { code, source } = diag;
const category = DiagnosticCategory[diag.category].toLowerCase();
return includeFileName ? { start, end, text, code, category, source, fileName: diag.file && diag.file.fileName } :
{ start, end, text, code, category, source };
}
export interface PendingErrorCheck {
@@ -112,7 +118,13 @@ namespace ts.server {
return true;
}
export import CommandNames = protocol.CommandTypes;
// CommandNames used to be exposed before TS 2.4 as a namespace
// In TS 2.4 we switched to an enum, keep this for backward compatibility
// The var assignment ensures that even though CommandTypes are a const enum
// we want to ensure the value is maintained in the out since the file is
// built using --preseveConstEnum.
export type CommandNames = protocol.CommandTypes;
export const CommandNames = (<any>protocol).CommandTypes;
export function formatMessage<T extends protocol.Message>(msg: T, logger: server.Logger, byteLength: (s: string, encoding: string) => number, newLine: string): string {
const verboseLogging = logger.hasLevel(LogLevel.verbose);
@@ -150,19 +162,13 @@ namespace ts.server {
* Represents operation that can schedule its next step to be executed later.
* Scheduling is done via instance of NextStep. If on current step subsequent step was not scheduled - operation is assumed to be completed.
*/
class MultistepOperation {
class MultistepOperation implements NextStep {
private requestId: number;
private timerHandle: any;
private immediateId: any;
private completed = true;
private readonly next: NextStep;
constructor(private readonly operationHost: MultistepOperationHost) {
this.next = {
immediate: action => this.immediate(action),
delay: (ms, action) => this.delay(ms, action)
};
}
constructor(private readonly operationHost: MultistepOperationHost) {}
public startNew(action: (next: NextStep) => void) {
this.complete();
@@ -182,7 +188,7 @@ namespace ts.server {
this.setImmediateId(undefined);
}
private immediate(action: () => void) {
public immediate(action: () => void) {
const requestId = this.requestId;
Debug.assert(requestId === this.operationHost.getCurrentRequestId(), "immediate: incorrect request id");
this.setImmediateId(this.operationHost.getServerHost().setImmediate(() => {
@@ -191,7 +197,7 @@ namespace ts.server {
}));
}
private delay(ms: number, action: () => void) {
public delay(ms: number, action: () => void) {
const requestId = this.requestId;
Debug.assert(requestId === this.operationHost.getCurrentRequestId(), "delay: incorrect request id");
this.setTimerHandle(this.operationHost.getServerHost().setTimeout(() => {
@@ -207,7 +213,7 @@ namespace ts.server {
stop = true;
}
else {
action(this.next);
action(this);
}
}
catch (e) {
@@ -377,8 +383,8 @@ namespace ts.server {
this.host.write(formatMessage(msg, this.logger, this.byteLength, this.host.newLine));
}
public configFileDiagnosticEvent(triggerFile: string, configFile: string, diagnostics: ts.Diagnostic[]) {
const bakedDiags = ts.map(diagnostics, formatConfigFileDiag);
public configFileDiagnosticEvent(triggerFile: string, configFile: string, diagnostics: Diagnostic[]) {
const bakedDiags = map(diagnostics, diagnostic => formatConfigFileDiag(diagnostic, /*includeFileName*/ true));
const ev: protocol.ConfigFileDiagnosticEvent = {
seq: 0,
type: "event",
@@ -427,7 +433,7 @@ namespace ts.server {
}
const bakedDiags = diags.map((diag) => formatDiag(file, project, diag));
this.event<protocol.DiagnosticEventBody>({ file: file, diagnostics: bakedDiags }, "semanticDiag");
this.event<protocol.DiagnosticEventBody>({ file, diagnostics: bakedDiags }, "semanticDiag");
}
catch (err) {
this.logError(err, "semantic check");
@@ -439,7 +445,7 @@ namespace ts.server {
const diags = project.getLanguageService().getSyntacticDiagnostics(file);
if (diags) {
const bakedDiags = diags.map((diag) => formatDiag(file, project, diag));
this.event<protocol.DiagnosticEventBody>({ file: file, diagnostics: bakedDiags }, "syntaxDiag");
this.event<protocol.DiagnosticEventBody>({ file, diagnostics: bakedDiags }, "syntaxDiag");
}
}
catch (err) {
@@ -511,9 +517,55 @@ namespace ts.server {
return projectFileName && this.projectService.findProject(projectFileName);
}
private getConfigFileAndProject(args: protocol.FileRequestArgs) {
const project = this.getProject(args.projectFileName);
const file = toNormalizedPath(args.file);
return {
configFile: project && project.hasConfigFile(file) && file,
project
};
}
private getConfigFileDiagnostics(configFile: NormalizedPath, project: Project, includeLinePosition: boolean) {
const projectErrors = project.getAllProjectErrors();
const optionsErrors = project.getLanguageService().getCompilerOptionsDiagnostics();
const diagnosticsForConfigFile = filter(
concatenate(projectErrors, optionsErrors),
diagnostic => diagnostic.file && diagnostic.file.fileName === configFile
);
return includeLinePosition ?
this.convertToDiagnosticsWithLinePositionFromDiagnosticFile(diagnosticsForConfigFile) :
map(
diagnosticsForConfigFile,
diagnostic => formatConfigFileDiag(diagnostic, /*includeFileName*/ false)
);
}
private convertToDiagnosticsWithLinePositionFromDiagnosticFile(diagnostics: Diagnostic[]) {
return diagnostics.map(d => <protocol.DiagnosticWithLinePosition>{
message: flattenDiagnosticMessageText(d.messageText, this.host.newLine),
start: d.start,
length: d.length,
category: DiagnosticCategory[d.category].toLowerCase(),
code: d.code,
startLocation: d.file && convertToLocation(getLineAndCharacterOfPosition(d.file, d.start)),
endLocation: d.file && convertToLocation(getLineAndCharacterOfPosition(d.file, d.start + d.length))
});
}
private getCompilerOptionsDiagnostics(args: protocol.CompilerOptionsDiagnosticsRequestArgs) {
const project = this.getProject(args.projectFileName);
return this.convertToDiagnosticsWithLinePosition(project.getLanguageService().getCompilerOptionsDiagnostics(), /*scriptInfo*/ undefined);
// Get diagnostics that dont have associated file with them
// The diagnostics which have file would be in config file and
// would be reported as part of configFileDiagnostics
return this.convertToDiagnosticsWithLinePosition(
filter(
project.getLanguageService().getCompilerOptionsDiagnostics(),
diagnostic => !diagnostic.file
),
/*scriptInfo*/ undefined
);
}
private convertToDiagnosticsWithLinePosition(diagnostics: Diagnostic[], scriptInfo: ScriptInfo) {
@@ -557,7 +609,7 @@ namespace ts.server {
return {
file: def.fileName,
start: defScriptInfo.positionToLineOffset(def.textSpan.start),
end: defScriptInfo.positionToLineOffset(ts.textSpanEnd(def.textSpan))
end: defScriptInfo.positionToLineOffset(textSpanEnd(def.textSpan))
};
});
}
@@ -581,7 +633,7 @@ namespace ts.server {
return {
file: def.fileName,
start: defScriptInfo.positionToLineOffset(def.textSpan.start),
end: defScriptInfo.positionToLineOffset(ts.textSpanEnd(def.textSpan))
end: defScriptInfo.positionToLineOffset(textSpanEnd(def.textSpan))
};
});
}
@@ -599,7 +651,7 @@ namespace ts.server {
return {
file: fileName,
start: scriptInfo.positionToLineOffset(textSpan.start),
end: scriptInfo.positionToLineOffset(ts.textSpanEnd(textSpan))
end: scriptInfo.positionToLineOffset(textSpanEnd(textSpan))
};
});
}
@@ -623,7 +675,7 @@ namespace ts.server {
const { fileName, isWriteAccess, textSpan, isInString } = occurrence;
const scriptInfo = project.getScriptInfo(fileName);
const start = scriptInfo.positionToLineOffset(textSpan.start);
const end = scriptInfo.positionToLineOffset(ts.textSpanEnd(textSpan));
const end = scriptInfo.positionToLineOffset(textSpanEnd(textSpan));
const result: protocol.OccurrencesResponseItem = {
start,
end,
@@ -639,10 +691,20 @@ namespace ts.server {
}
private getSyntacticDiagnosticsSync(args: protocol.SyntacticDiagnosticsSyncRequestArgs): protocol.Diagnostic[] | protocol.DiagnosticWithLinePosition[] {
const { configFile } = this.getConfigFileAndProject(args);
if (configFile) {
// all the config file errors are reported as part of semantic check so nothing to report here
return [];
}
return this.getDiagnosticsWorker(args, /*isSemantic*/ false, (project, file) => project.getLanguageService().getSyntacticDiagnostics(file), args.includeLinePosition);
}
private getSemanticDiagnosticsSync(args: protocol.SemanticDiagnosticsSyncRequestArgs): protocol.Diagnostic[] | protocol.DiagnosticWithLinePosition[] {
const { configFile, project } = this.getConfigFileAndProject(args);
if (configFile) {
return this.getConfigFileDiagnostics(configFile, project, args.includeLinePosition);
}
return this.getDiagnosticsWorker(args, /*isSemantic*/ true, (project, file) => project.getLanguageService().getSemanticDiagnostics(file), args.includeLinePosition);
}
@@ -663,7 +725,7 @@ namespace ts.server {
return documentHighlights;
}
function convertToDocumentHighlightsItem(documentHighlights: ts.DocumentHighlights): ts.server.protocol.DocumentHighlightsItem {
function convertToDocumentHighlightsItem(documentHighlights: DocumentHighlights): protocol.DocumentHighlightsItem {
const { fileName, highlightSpans } = documentHighlights;
const scriptInfo = project.getScriptInfo(fileName);
@@ -672,10 +734,10 @@ namespace ts.server {
highlightSpans: highlightSpans.map(convertHighlightSpan)
};
function convertHighlightSpan(highlightSpan: ts.HighlightSpan): ts.server.protocol.HighlightSpan {
function convertHighlightSpan(highlightSpan: HighlightSpan): protocol.HighlightSpan {
const { textSpan, kind } = highlightSpan;
const start = scriptInfo.positionToLineOffset(textSpan.start);
const end = scriptInfo.positionToLineOffset(ts.textSpanEnd(textSpan));
const end = scriptInfo.positionToLineOffset(textSpanEnd(textSpan));
return { start, end, kind };
}
}
@@ -686,15 +748,15 @@ namespace ts.server {
}
private getProjectInfo(args: protocol.ProjectInfoRequestArgs): protocol.ProjectInfo {
return this.getProjectInfoWorker(args.file, args.projectFileName, args.needFileNameList);
return this.getProjectInfoWorker(args.file, args.projectFileName, args.needFileNameList, /*excludeConfigFiles*/ false);
}
private getProjectInfoWorker(uncheckedFileName: string, projectFileName: string, needFileNameList: boolean) {
private getProjectInfoWorker(uncheckedFileName: string, projectFileName: string, needFileNameList: boolean, excludeConfigFiles: boolean) {
const { project } = this.getFileAndProjectWorker(uncheckedFileName, projectFileName, /*refreshInferredProjects*/ true, /*errorOnMissingProject*/ true);
const projectInfo = {
configFileName: project.getProjectName(),
languageServiceDisabled: !project.languageServiceEnabled,
fileNames: needFileNameList ? project.getFileNames() : undefined
fileNames: needFileNameList ? project.getFileNames(/*excludeFilesFromExternalLibraries*/ false, excludeConfigFiles) : undefined
};
return projectInfo;
}
@@ -718,7 +780,7 @@ namespace ts.server {
const scriptInfo = this.projectService.getScriptInfo(args.file);
projects = scriptInfo.containingProjects;
}
// ts.filter handles case when 'projects' is undefined
// filter handles case when 'projects' is undefined
projects = filter(projects, p => p.languageServiceEnabled);
if (!projects || !projects.length) {
return Errors.ThrowNoProject();
@@ -771,28 +833,29 @@ namespace ts.server {
return <protocol.FileSpan>{
file: location.fileName,
start: locationScriptInfo.positionToLineOffset(location.textSpan.start),
end: locationScriptInfo.positionToLineOffset(ts.textSpanEnd(location.textSpan)),
end: locationScriptInfo.positionToLineOffset(textSpanEnd(location.textSpan)),
};
});
},
compareRenameLocation,
(a, b) => a.file === b.file && a.start.line === b.start.line && a.start.offset === b.start.offset
);
const locs = fileSpans.reduce<protocol.SpanGroup[]>((accum, cur) => {
const locs: protocol.SpanGroup[] = [];
for (const cur of fileSpans) {
let curFileAccum: protocol.SpanGroup;
if (accum.length > 0) {
curFileAccum = accum[accum.length - 1];
if (locs.length > 0) {
curFileAccum = locs[locs.length - 1];
if (curFileAccum.file !== cur.file) {
curFileAccum = undefined;
}
}
if (!curFileAccum) {
curFileAccum = { file: cur.file, locs: [] };
accum.push(curFileAccum);
locs.push(curFileAccum);
}
curFileAccum.locs.push({ start: cur.start, end: cur.end });
return accum;
}, []);
}
return { info: renameInfo, locs };
}
@@ -852,10 +915,10 @@ namespace ts.server {
return undefined;
}
const displayString = ts.displayPartsToString(nameInfo.displayParts);
const displayString = displayPartsToString(nameInfo.displayParts);
const nameSpan = nameInfo.textSpan;
const nameColStart = scriptInfo.positionToLineOffset(nameSpan.start).offset;
const nameText = scriptInfo.getSnapshot().getText(nameSpan.start, ts.textSpanEnd(nameSpan));
const nameText = scriptInfo.getSnapshot().getText(nameSpan.start, textSpanEnd(nameSpan));
const refs = combineProjectOutput<protocol.ReferencesResponseItem>(
projects,
(project: Project) => {
@@ -868,12 +931,12 @@ namespace ts.server {
const refScriptInfo = project.getScriptInfo(ref.fileName);
const start = refScriptInfo.positionToLineOffset(ref.textSpan.start);
const refLineSpan = refScriptInfo.lineToTextSpan(start.line - 1);
const lineText = refScriptInfo.getSnapshot().getText(refLineSpan.start, ts.textSpanEnd(refLineSpan)).replace(/\r|\n/g, "");
const lineText = refScriptInfo.getSnapshot().getText(refLineSpan.start, textSpanEnd(refLineSpan)).replace(/\r|\n/g, "");
return {
file: ref.fileName,
start: start,
lineText: lineText,
end: refScriptInfo.positionToLineOffset(ts.textSpanEnd(ref.textSpan)),
start,
lineText,
end: refScriptInfo.positionToLineOffset(textSpanEnd(ref.textSpan)),
isWriteAccess: ref.isWriteAccess,
isDefinition: ref.isDefinition
};
@@ -1004,15 +1067,15 @@ namespace ts.server {
}
if (simplifiedResult) {
const displayString = ts.displayPartsToString(quickInfo.displayParts);
const docString = ts.displayPartsToString(quickInfo.documentation);
const displayString = displayPartsToString(quickInfo.displayParts);
const docString = displayPartsToString(quickInfo.documentation);
return {
kind: quickInfo.kind,
kindModifiers: quickInfo.kindModifiers,
start: scriptInfo.positionToLineOffset(quickInfo.textSpan.start),
end: scriptInfo.positionToLineOffset(ts.textSpanEnd(quickInfo.textSpan)),
displayString: displayString,
end: scriptInfo.positionToLineOffset(textSpanEnd(quickInfo.textSpan)),
displayString,
documentation: docString,
tags: quickInfo.tags || []
};
@@ -1071,32 +1134,29 @@ namespace ts.server {
// only to the previous line. If all this is true, then
// add edits necessary to properly indent the current line.
if ((args.key === "\n") && ((!edits) || (edits.length === 0) || allEditsBeforePos(edits, position))) {
const lineInfo = scriptInfo.getLineInfo(args.line);
if (lineInfo && (lineInfo.leaf) && (lineInfo.leaf.text)) {
const lineText = lineInfo.leaf.text;
if (lineText.search("\\S") < 0) {
const preferredIndent = project.getLanguageService(/*ensureSynchronized*/ false).getIndentationAtPosition(file, position, formatOptions);
let hasIndent = 0;
let i: number, len: number;
for (i = 0, len = lineText.length; i < len; i++) {
if (lineText.charAt(i) === " ") {
hasIndent++;
}
else if (lineText.charAt(i) === "\t") {
hasIndent += formatOptions.tabSize;
}
else {
break;
}
const { lineText, absolutePosition } = scriptInfo.getLineInfo(args.line);
if (lineText && lineText.search("\\S") < 0) {
const preferredIndent = project.getLanguageService(/*ensureSynchronized*/ false).getIndentationAtPosition(file, position, formatOptions);
let hasIndent = 0;
let i: number, len: number;
for (i = 0, len = lineText.length; i < len; i++) {
if (lineText.charAt(i) === " ") {
hasIndent++;
}
// i points to the first non whitespace character
if (preferredIndent !== hasIndent) {
const firstNoWhiteSpacePosition = lineInfo.offset + i;
edits.push({
span: ts.createTextSpanFromBounds(lineInfo.offset, firstNoWhiteSpacePosition),
newText: formatting.getIndentationString(preferredIndent, formatOptions)
});
else if (lineText.charAt(i) === "\t") {
hasIndent += formatOptions.tabSize;
}
else {
break;
}
}
// i points to the first non whitespace character
if (preferredIndent !== hasIndent) {
const firstNoWhiteSpacePosition = absolutePosition + i;
edits.push({
span: createTextSpanFromBounds(absolutePosition, firstNoWhiteSpacePosition),
newText: formatting.getIndentationString(preferredIndent, formatOptions)
});
}
}
}
@@ -1108,7 +1168,7 @@ namespace ts.server {
return edits.map((edit) => {
return {
start: scriptInfo.positionToLineOffset(edit.span.start),
end: scriptInfo.positionToLineOffset(ts.textSpanEnd(edit.span)),
end: scriptInfo.positionToLineOffset(textSpanEnd(edit.span)),
newText: edit.newText ? edit.newText : ""
};
});
@@ -1126,15 +1186,13 @@ namespace ts.server {
return undefined;
}
if (simplifiedResult) {
return completions.entries.reduce((result: protocol.CompletionEntry[], entry: ts.CompletionEntry) => {
return mapDefined(completions.entries, entry => {
if (completions.isMemberCompletion || (entry.name.toLowerCase().indexOf(prefix.toLowerCase()) === 0)) {
const { name, kind, kindModifiers, sortText, replacementSpan } = entry;
const convertedSpan: protocol.TextSpan =
replacementSpan ? this.decorateSpan(replacementSpan, scriptInfo) : undefined;
result.push({ name, kind, kindModifiers, sortText, replacementSpan: convertedSpan });
const convertedSpan = replacementSpan ? this.decorateSpan(replacementSpan, scriptInfo) : undefined;
return { name, kind, kindModifiers, sortText, replacementSpan: convertedSpan };
}
return result;
}, []).sort((a, b) => ts.compareStrings(a.name, b.name));
}).sort((a, b) => compareStrings(a.name, b.name));
}
else {
return completions;
@@ -1146,13 +1204,8 @@ namespace ts.server {
const scriptInfo = project.getScriptInfoForNormalizedPath(file);
const position = this.getPosition(args, scriptInfo);
return args.entryNames.reduce((accum: protocol.CompletionEntryDetails[], entryName: string) => {
const details = project.getLanguageService().getCompletionEntryDetails(file, position, entryName);
if (details) {
accum.push(details);
}
return accum;
}, []);
return mapDefined(args.entryNames, entryName =>
project.getLanguageService().getCompletionEntryDetails(file, position, entryName));
}
private getCompileOnSaveAffectedFileList(args: protocol.FileRequestArgs): protocol.CompileOnSaveAffectedFileListSingleProject[] {
@@ -1217,14 +1270,11 @@ namespace ts.server {
}
private getDiagnostics(next: NextStep, delay: number, fileNames: string[]): void {
const checkList = fileNames.reduce((accum: PendingErrorCheck[], uncheckedFileName: string) => {
const checkList = mapDefined(fileNames, uncheckedFileName => {
const fileName = toNormalizedPath(uncheckedFileName);
const project = this.projectService.getDefaultProjectForFile(fileName, /*refreshInferredProjects*/ true);
if (project) {
accum.push({ fileName, project });
}
return accum;
}, []);
return project && { fileName, project };
});
if (checkList.length > 0) {
this.updateErrorCheck(next, checkList, this.changeSeq, (n) => n === this.changeSeq, delay);
@@ -1269,11 +1319,11 @@ namespace ts.server {
if (!fileName) {
return;
}
const file = ts.normalizePath(fileName);
const file = normalizePath(fileName);
this.projectService.closeClientFile(file);
}
private decorateNavigationBarItems(items: ts.NavigationBarItem[], scriptInfo: ScriptInfo): protocol.NavigationBarItem[] {
private decorateNavigationBarItems(items: NavigationBarItem[], scriptInfo: ScriptInfo): protocol.NavigationBarItem[] {
return map(items, item => ({
text: item.text,
kind: item.kind,
@@ -1294,7 +1344,7 @@ namespace ts.server {
: items;
}
private decorateNavigationTree(tree: ts.NavigationTree, scriptInfo: ScriptInfo): protocol.NavigationTree {
private decorateNavigationTree(tree: NavigationTree, scriptInfo: ScriptInfo): protocol.NavigationTree {
return {
text: tree.text,
kind: tree.kind,
@@ -1307,7 +1357,7 @@ namespace ts.server {
private decorateSpan(span: TextSpan, scriptInfo: ScriptInfo): protocol.TextSpan {
return {
start: scriptInfo.positionToLineOffset(span.start),
end: scriptInfo.positionToLineOffset(ts.textSpanEnd(span))
end: scriptInfo.positionToLineOffset(textSpanEnd(span))
};
}
@@ -1337,13 +1387,13 @@ namespace ts.server {
return navItems.map((navItem) => {
const scriptInfo = project.getScriptInfo(navItem.fileName);
const start = scriptInfo.positionToLineOffset(navItem.textSpan.start);
const end = scriptInfo.positionToLineOffset(ts.textSpanEnd(navItem.textSpan));
const end = scriptInfo.positionToLineOffset(textSpanEnd(navItem.textSpan));
const bakedItem: protocol.NavtoItem = {
name: navItem.name,
kind: navItem.kind,
file: navItem.fileName,
start: start,
end: end,
start,
end,
};
if (navItem.kindModifiers && (navItem.kindModifiers !== "")) {
bakedItem.kindModifiers = navItem.kindModifiers;
@@ -1402,7 +1452,7 @@ namespace ts.server {
}
private getSupportedCodeFixes(): string[] {
return ts.getSupportedCodeFixes();
return getSupportedCodeFixes();
}
private isLocation(locationOrSpan: protocol.FileLocationOrRangeRequestArgs): locationOrSpan is protocol.FileLocationRequestArgs {
@@ -1433,29 +1483,40 @@ namespace ts.server {
return project.getLanguageService().getApplicableRefactors(file, position || textRange);
}
private getRefactorCodeActions(args: protocol.GetRefactorCodeActionsRequestArgs, simplifiedResult: boolean): protocol.RefactorCodeActions | protocol.RefactorCodeActionsFull {
private getEditsForRefactor(args: protocol.GetEditsForRefactorRequestArgs, simplifiedResult: boolean): RefactorEditInfo | protocol.RefactorEditInfo {
const { file, project } = this.getFileAndProjectWithoutRefreshingInferredProjects(args);
const scriptInfo = project.getScriptInfoForNormalizedPath(file);
const { position, textRange } = this.extractPositionAndRange(args, scriptInfo);
const result: ts.CodeAction[] = project.getLanguageService().getRefactorCodeActions(
const result = project.getLanguageService().getEditsForRefactor(
file,
this.projectService.getFormatCodeOptions(),
position || textRange,
args.refactorName
args.refactor,
args.action
);
if (simplifiedResult) {
// Not full
if (result === undefined) {
return {
actions: result.map(action => this.mapCodeAction(action, scriptInfo))
edits: []
};
}
if (simplifiedResult) {
const file = result.renameFilename;
let location: protocol.Location | undefined;
if (file !== undefined && result.renameLocation !== undefined) {
const renameScriptInfo = project.getScriptInfoForNormalizedPath(toNormalizedPath(file));
location = renameScriptInfo.positionToLineOffset(result.renameLocation);
}
return {
renameLocation: location,
renameFilename: file,
edits: result.edits.map(change => this.mapTextChangesToCodeEdits(project, change))
};
}
else {
// Full
return {
actions: result
};
return result;
}
}
@@ -1513,6 +1574,14 @@ namespace ts.server {
};
}
private mapTextChangesToCodeEdits(project: Project, textChanges: FileTextChanges): protocol.FileCodeEdits {
const scriptInfo = project.getScriptInfoForNormalizedPath(toNormalizedPath(textChanges.fileName));
return {
fileName: textChanges.fileName,
textChanges: textChanges.textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, scriptInfo))
};
}
private convertTextChangeToCodeEdit(change: ts.TextChange, scriptInfo: ScriptInfo): protocol.CodeEdit {
return {
start: scriptInfo.positionToLineOffset(change.span.start),
@@ -1536,7 +1605,7 @@ namespace ts.server {
}
private getDiagnosticsForProject(next: NextStep, delay: number, fileName: string): void {
const { fileNames, languageServiceDisabled } = this.getProjectInfoWorker(fileName, /*projectFileName*/ undefined, /*needFileNameList*/ true);
const { fileNames, languageServiceDisabled } = this.getProjectInfoWorker(fileName, /*projectFileName*/ undefined, /*needFileNameList*/ true, /*excludeConfigFiles*/ true);
if (languageServiceDisabled) {
return;
}
@@ -1552,18 +1621,22 @@ namespace ts.server {
const normalizedFileName = toNormalizedPath(fileName);
const project = this.projectService.getDefaultProjectForFile(normalizedFileName, /*refreshInferredProjects*/ true);
for (const fileNameInProject of fileNamesInProject) {
if (this.getCanonicalFileName(fileNameInProject) === this.getCanonicalFileName(fileName))
if (this.getCanonicalFileName(fileNameInProject) === this.getCanonicalFileName(fileName)) {
highPriorityFiles.push(fileNameInProject);
}
else {
const info = this.projectService.getScriptInfo(fileNameInProject);
if (!info.isScriptOpen()) {
if (fileNameInProject.indexOf(".d.ts") > 0)
if (fileNameInProject.indexOf(Extension.Dts) > 0) {
veryLowPriorityFiles.push(fileNameInProject);
else
}
else {
lowPriorityFiles.push(fileNameInProject);
}
}
else
else {
mediumPriorityFiles.push(fileNameInProject);
}
}
}
@@ -1579,7 +1652,7 @@ namespace ts.server {
getCanonicalFileName(fileName: string) {
const name = this.host.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase();
return ts.normalizePath(name);
return normalizePath(name);
}
exit() {
@@ -1844,11 +1917,11 @@ namespace ts.server {
[CommandNames.GetApplicableRefactors]: (request: protocol.GetApplicableRefactorsRequest) => {
return this.requiredResponse(this.getApplicableRefactors(request.arguments));
},
[CommandNames.GetRefactorCodeActions]: (request: protocol.GetRefactorCodeActionsRequest) => {
return this.requiredResponse(this.getRefactorCodeActions(request.arguments, /*simplifiedResult*/ true));
[CommandNames.GetEditsForRefactor]: (request: protocol.GetEditsForRefactorRequest) => {
return this.requiredResponse(this.getEditsForRefactor(request.arguments, /*simplifiedResult*/ true));
},
[CommandNames.GetRefactorCodeActionsFull]: (request: protocol.GetRefactorCodeActionsRequest) => {
return this.requiredResponse(this.getRefactorCodeActions(request.arguments, /*simplifiedResult*/ false));
[CommandNames.GetEditsForRefactorFull]: (request: protocol.GetEditsForRefactorRequest) => {
return this.requiredResponse(this.getEditsForRefactor(request.arguments, /*simplifiedResult*/ false));
}
});
+1
View File
@@ -2,6 +2,7 @@
"compilerOptions": {
"noImplicitAny": true,
"noImplicitThis": true,
"alwaysStrict": true,
"preserveConstEnums": true,
"pretty": true,
"outFile": "../../built/local/tsserverlibrary.js",
+10 -6
View File
@@ -20,8 +20,12 @@ declare namespace ts.server {
require?(initialPath: string, moduleName: string): RequireResult;
}
export interface SortedArray<T> extends Array<T> {
" __sortedArrayBrand": any;
}
export interface SortedReadonlyArray<T> extends ReadonlyArray<T> {
" __sortedReadonlyArrayBrand": any;
" __sortedArrayBrand": any;
}
export interface TypingInstallerRequest {
@@ -31,9 +35,9 @@ declare namespace ts.server {
export interface DiscoverTypings extends TypingInstallerRequest {
readonly fileNames: string[];
readonly projectRootPath: ts.Path;
readonly compilerOptions: ts.CompilerOptions;
readonly typeAcquisition: ts.TypeAcquisition;
readonly projectRootPath: Path;
readonly compilerOptions: CompilerOptions;
readonly typeAcquisition: TypeAcquisition;
readonly unresolvedImports: SortedReadonlyArray<string>;
readonly cachePath?: string;
readonly kind: "discover";
@@ -63,8 +67,8 @@ declare namespace ts.server {
}
export interface SetTypings extends ProjectResponse {
readonly typeAcquisition: ts.TypeAcquisition;
readonly compilerOptions: ts.CompilerOptions;
readonly typeAcquisition: TypeAcquisition;
readonly compilerOptions: CompilerOptions;
readonly typings: string[];
readonly unresolvedImports: SortedReadonlyArray<string>;
readonly kind: ActionSet;
+1 -1
View File
@@ -110,7 +110,7 @@ namespace ts.server {
this.perProjectCache.set(projectName, {
compilerOptions,
typeAcquisition,
typings: toSortedReadonlyArray(newTypings),
typings: toSortedArray(newTypings),
unresolvedImports,
poisoned: false
});
@@ -85,6 +85,7 @@ namespace ts.server.typingsInstaller {
private readonly missingTypingsSet: Map<true> = createMap<true>();
private readonly knownCachesSet: Map<true> = createMap<true>();
private readonly projectWatchers: Map<FileWatcher[]> = createMap<FileWatcher[]>();
private safeList: JsTyping.SafeList | undefined;
readonly pendingRunRequests: PendingRequest[] = [];
private installRunCount = 1;
@@ -93,10 +94,10 @@ namespace ts.server.typingsInstaller {
abstract readonly typesRegistry: Map<void>;
constructor(
readonly installTypingHost: InstallTypingHost,
readonly globalCachePath: string,
readonly safeListPath: Path,
readonly throttleLimit: number,
protected readonly installTypingHost: InstallTypingHost,
private readonly globalCachePath: string,
private readonly safeListPath: Path,
private readonly throttleLimit: number,
protected readonly log = nullLog) {
if (this.log.isEnabled()) {
this.log.writeLine(`Global cache location '${globalCachePath}', safe file path '${safeListPath}'`);
@@ -143,11 +144,15 @@ namespace ts.server.typingsInstaller {
this.processCacheLocation(req.cachePath);
}
if (this.safeList === undefined) {
this.safeList = JsTyping.loadSafeList(this.installTypingHost, this.safeListPath);
}
const discoverTypingsResult = JsTyping.discoverTypings(
this.installTypingHost,
this.log.isEnabled() ? this.log.writeLine : undefined,
req.fileNames,
req.projectRootPath,
this.safeListPath,
this.safeList,
this.packageNameToTypingLocation,
req.typeAcquisition,
req.unresolvedImports);
@@ -389,7 +394,7 @@ namespace ts.server.typingsInstaller {
this.log.writeLine(`Got FS notification for ${f}, handler is already invoked '${isInvoked}'`);
}
if (!isInvoked) {
this.sendResponse({ projectName: projectName, kind: server.ActionInvalidate });
this.sendResponse({ projectName, kind: server.ActionInvalidate });
isInvoked = true;
}
}, /*pollingInterval*/ 2000);
@@ -434,5 +439,4 @@ namespace ts.server.typingsInstaller {
export function typingsName(packageName: string): string {
return `@types/${packageName}@ts${versionMajorMinor}`;
}
const versionMajorMinor = version.split(".").slice(0, 2).join(".");
}
+74 -55
View File
@@ -9,7 +9,7 @@ namespace ts.server {
verbose
}
export const emptyArray: ReadonlyArray<any> = [];
export const emptyArray: SortedReadonlyArray<never> = createSortedArray<never>();
export interface Logger {
close(): void;
@@ -49,7 +49,7 @@ namespace ts.server {
export function createInstallTypingsRequest(project: Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray<string>, cachePath?: string): DiscoverTypings {
return {
projectName: project.getProjectName(),
fileNames: project.getFileNames(/*excludeFilesFromExternalLibraries*/ true),
fileNames: project.getFileNames(/*excludeFilesFromExternalLibraries*/ true, /*excludeConfigFiles*/ true),
compilerOptions: project.getCompilerOptions(),
typeAcquisition,
unresolvedImports,
@@ -77,7 +77,7 @@ namespace ts.server {
tabSize: 4,
newLineCharacter: host.newLine || "\n",
convertTabsToSpaces: true,
indentStyle: ts.IndentStyle.Smart,
indentStyle: IndentStyle.Smart,
insertSpaceAfterConstructor: false,
insertSpaceAfterCommaDelimiter: true,
insertSpaceAfterSemicolonInForStatements: true,
@@ -103,24 +103,6 @@ namespace ts.server {
}
}
export function removeItemFromSet<T>(items: T[], itemToRemove: T) {
if (items.length === 0) {
return;
}
const index = items.indexOf(itemToRemove);
if (index < 0) {
return;
}
if (index === items.length - 1) {
// last item - pop it
items.pop();
}
else {
// non-last item - replace it with the last one
items[index] = items.pop();
}
}
export type NormalizedPath = string & { __normalizedPathTag: any };
export function toNormalizedPath(fileName: string): NormalizedPath {
@@ -190,40 +172,8 @@ namespace ts.server {
return `/dev/null/inferredProject${counter}*`;
}
export function toSortedReadonlyArray(arr: string[]): SortedReadonlyArray<string> {
arr.sort();
return <any>arr;
}
export function enumerateInsertsAndDeletes<T>(a: SortedReadonlyArray<T>, b: SortedReadonlyArray<T>, inserted: (item: T) => void, deleted: (item: T) => void, compare?: (a: T, b: T) => Comparison) {
compare = compare || ts.compareValues;
let aIndex = 0;
let bIndex = 0;
const aLen = a.length;
const bLen = b.length;
while (aIndex < aLen && bIndex < bLen) {
const aItem = a[aIndex];
const bItem = b[bIndex];
const compareResult = compare(aItem, bItem);
if (compareResult === Comparison.LessThan) {
inserted(aItem);
aIndex++;
}
else if (compareResult === Comparison.GreaterThan) {
deleted(bItem);
bIndex++;
}
else {
aIndex++;
bIndex++;
}
}
while (aIndex < aLen) {
inserted(a[aIndex++]);
}
while (bIndex < bLen) {
deleted(b[bIndex++]);
}
export function createSortedArray<T>(): SortedArray<T> {
return [] as SortedArray<T>;
}
export class ThrottledOperations {
@@ -273,4 +223,73 @@ namespace ts.server {
}
}
}
}
/* @internal */
namespace ts.server {
export function insertSorted<T>(array: SortedArray<T>, insert: T, compare: Comparer<T>): void {
if (array.length === 0) {
array.push(insert);
return;
}
const insertIndex = binarySearch(array, insert, compare);
if (insertIndex < 0) {
array.splice(~insertIndex, 0, insert);
}
}
export function removeSorted<T>(array: SortedArray<T>, remove: T, compare: Comparer<T>): void {
if (!array || array.length === 0) {
return;
}
if (array[0] === remove) {
array.splice(0, 1);
return;
}
const removeIndex = binarySearch(array, remove, compare);
if (removeIndex >= 0) {
array.splice(removeIndex, 1);
}
}
export function toSortedArray(arr: string[]): SortedArray<string>;
export function toSortedArray<T>(arr: T[], comparer: Comparer<T>): SortedArray<T>;
export function toSortedArray<T>(arr: T[], comparer?: Comparer<T>): SortedArray<T> {
arr.sort(comparer);
return arr as SortedArray<T>;
}
export function enumerateInsertsAndDeletes<T>(newItems: SortedReadonlyArray<T>, oldItems: SortedReadonlyArray<T>, inserted: (newItem: T) => void, deleted: (oldItem: T) => void, compare?: Comparer<T>) {
compare = compare || compareValues;
let newIndex = 0;
let oldIndex = 0;
const newLen = newItems.length;
const oldLen = oldItems.length;
while (newIndex < newLen && oldIndex < oldLen) {
const newItem = newItems[newIndex];
const oldItem = oldItems[oldIndex];
const compareResult = compare(newItem, oldItem);
if (compareResult === Comparison.LessThan) {
inserted(newItem);
newIndex++;
}
else if (compareResult === Comparison.GreaterThan) {
deleted(oldItem);
oldIndex++;
}
else {
newIndex++;
oldIndex++;
}
}
while (newIndex < newLen) {
inserted(newItems[newIndex++]);
}
while (oldIndex < oldLen) {
deleted(oldItems[oldIndex++]);
}
}
}