Merge branch 'master' into glob2

Conflicts:
	Jakefile.js
	src/compiler/commandLineParser.ts
	src/compiler/core.ts
	src/compiler/sys.ts
	src/harness/harness.ts
	src/server/editorServices.ts
	src/services/shims.ts
	tests/cases/unittests/cachingInServerLSHost.ts
This commit is contained in:
Richard Knoll
2016-05-25 17:06:50 -07:00
4832 changed files with 356652 additions and 85393 deletions

View File

@@ -1,5 +1,5 @@
/// <reference path="session.ts" />
namespace ts.server {
export interface SessionClientHost extends LanguageServiceHost {
@@ -25,23 +25,23 @@ namespace ts.server {
private lineMaps: ts.Map<number[]> = {};
private messages: string[] = [];
private lastRenameEntry: RenameEntry;
constructor(private host: SessionClientHost) {
}
public onMessage(message: string): void {
public onMessage(message: string): void {
this.messages.push(message);
}
private writeMessage(message: string): void {
private writeMessage(message: string): void {
this.host.writeMessage(message);
}
private getLineMap(fileName: string): number[] {
private getLineMap(fileName: string): number[] {
var lineMap = ts.lookUp(this.lineMaps, fileName);
if (!lineMap) {
var scriptSnapshot = this.host.getScriptSnapshot(fileName);
lineMap = this.lineMaps[fileName] = ts.computeLineStarts(scriptSnapshot.getText(0, scriptSnapshot.getLength()));
lineMap = this.lineMaps[fileName] = ts.computeLineStarts(scriptSnapshot.getText(0, scriptSnapshot.getLength()));
}
return lineMap;
}
@@ -82,34 +82,29 @@ namespace ts.server {
}
private processResponse<T extends protocol.Response>(request: protocol.Request): T {
var lastMessage = this.messages.shift();
Debug.assert(!!lastMessage, "Did not recieve any responses.");
// Read the content length
var contentLengthPrefix = "Content-Length: ";
var lines = lastMessage.split("\r\n");
Debug.assert(lines.length >= 2, "Malformed response: Expected 3 lines in the response.");
var contentLengthText = lines[0];
Debug.assert(contentLengthText.indexOf(contentLengthPrefix) === 0, "Malformed response: Response text did not contain content-length header.");
var contentLength = parseInt(contentLengthText.substring(contentLengthPrefix.length));
// Read the body
var responseBody = lines[2];
// Verify content length
Debug.assert(responseBody.length + 1 === contentLength, "Malformed response: Content length did not match the response's body length.");
try {
var response: T = JSON.parse(responseBody);
}
catch (e) {
throw new Error("Malformed response: Failed to parse server response: " + lastMessage + ". \r\n Error details: " + e.message);
let foundResponseMessage = false;
let lastMessage: string;
let response: T;
while (!foundResponseMessage) {
lastMessage = this.messages.shift();
Debug.assert(!!lastMessage, "Did not receive any responses.");
const responseBody = processMessage(lastMessage);
try {
response = JSON.parse(responseBody);
// the server may emit events before emitting the response. We
// want to ignore these events for testing purpose.
if (response.type === "response") {
foundResponseMessage = true;
}
}
catch (e) {
throw new Error("Malformed response: Failed to parse server response: " + lastMessage + ". \r\n Error details: " + e.message);
}
}
// verify the sequence numbers
Debug.assert(response.request_seq === request.seq, "Malformed response: response sequance number did not match request sequence number.");
Debug.assert(response.request_seq === request.seq, "Malformed response: response sequence number did not match request sequence number.");
// unmarshal errors
if (!response.success) {
throw new Error("Error " + response.message);
@@ -118,10 +113,28 @@ namespace ts.server {
Debug.assert(!!response.body, "Malformed response: Unexpected empty response body.");
return response;
function processMessage(message: string) {
// Read the content length
const contentLengthPrefix = "Content-Length: ";
const lines = message.split("\r\n");
Debug.assert(lines.length >= 2, "Malformed response: Expected 3 lines in the response.");
const contentLengthText = lines[0];
Debug.assert(contentLengthText.indexOf(contentLengthPrefix) === 0, "Malformed response: Response text did not contain content-length header.");
const contentLength = parseInt(contentLengthText.substring(contentLengthPrefix.length));
// Read the body
const responseBody = lines[2];
// Verify content length
Debug.assert(responseBody.length + 1 === contentLength, "Malformed response: Content length did not match the response's body length.");
return responseBody;
}
}
openFile(fileName: string, content?: string): void {
var args: protocol.OpenRequestArgs = { file: fileName, fileContent: content };
openFile(fileName: string, content?: string, scriptKindName?: "TS" | "JS" | "TSX" | "JSX"): void {
var args: protocol.OpenRequestArgs = { file: fileName, fileContent: content, scriptKindName };
this.processRequest(CommandNames.Open, args);
}
@@ -186,7 +199,7 @@ namespace ts.server {
fileNames: response.body.fileNames
};
}
getCompletionsAtPosition(fileName: string, position: number): CompletionInfo {
var lineOffset = this.positionToOneBasedLineOffset(fileName, position);
var args: protocol.CompletionsRequestArgs = {
@@ -199,13 +212,13 @@ namespace ts.server {
var request = this.processRequest<protocol.CompletionsRequest>(CommandNames.Completions, args);
var response = this.processResponse<protocol.CompletionsResponse>(request);
return {
return {
isMemberCompletion: false,
isNewIdentifierLocation: false,
entries: response.body
};
}
getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails {
var lineOffset = this.positionToOneBasedLineOffset(fileName, position);
var args: protocol.CompletionDetailsRequestArgs = {
@@ -234,7 +247,7 @@ namespace ts.server {
var fileName = entry.file;
var start = this.lineOffsetToPosition(fileName, entry.start);
var end = this.lineOffsetToPosition(fileName, entry.end);
return {
name: entry.name,
containerName: entry.containerName || "",
@@ -264,7 +277,7 @@ namespace ts.server {
var request = this.processRequest<protocol.FormatRequest>(CommandNames.Format, args);
var response = this.processResponse<protocol.FormatResponse>(request);
return response.body.map(entry=> this.convertCodeEditsToTextChange(fileName, entry));
return response.body.map(entry => this.convertCodeEditsToTextChange(fileName, entry));
}
getFormattingEditsForDocument(fileName: string, options: ts.FormatCodeOptions): ts.TextChange[] {
@@ -284,7 +297,7 @@ namespace ts.server {
var request = this.processRequest<protocol.FormatOnKeyRequest>(CommandNames.Formatonkey, args);
var response = this.processResponse<protocol.FormatResponse>(request);
return response.body.map(entry=> this.convertCodeEditsToTextChange(fileName, entry));
return response.body.map(entry => this.convertCodeEditsToTextChange(fileName, entry));
}
getDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[] {
@@ -339,7 +352,7 @@ namespace ts.server {
});
}
findReferences(fileName: string, position: number): ReferencedSymbol[]{
findReferences(fileName: string, position: number): ReferencedSymbol[] {
// Not yet implemented.
return [];
}
@@ -444,7 +457,7 @@ namespace ts.server {
text: item.text,
kind: item.kind,
kindModifiers: item.kindModifiers || "",
spans: item.spans.map(span=> createTextSpanFromBounds(this.lineOffsetToPosition(fileName, span.start), this.lineOffsetToPosition(fileName, span.end))),
spans: item.spans.map(span => createTextSpanFromBounds(this.lineOffsetToPosition(fileName, span.start), this.lineOffsetToPosition(fileName, span.end))),
childItems: this.decodeNavigationBarItems(item.childItems, fileName),
indent: 0,
bolded: false,
@@ -478,10 +491,10 @@ namespace ts.server {
line: lineOffset.line,
offset: lineOffset.offset
};
var request = this.processRequest<protocol.SignatureHelpRequest>(CommandNames.SignatureHelp, args);
var response = this.processResponse<protocol.SignatureHelpResponse>(request);
if (!response.body) {
return undefined;
}
@@ -490,7 +503,7 @@ namespace ts.server {
var span = helpItems.applicableSpan;
var start = this.lineOffsetToPosition(fileName, span.start);
var end = this.lineOffsetToPosition(fileName, span.end);
var result: SignatureHelpItems = {
items: helpItems.items,
applicableSpan: {
@@ -499,7 +512,7 @@ namespace ts.server {
},
selectedItemIndex: helpItems.selectedItemIndex,
argumentIndex: helpItems.argumentIndex,
argumentCount: helpItems.argumentCount,
argumentCount: helpItems.argumentCount,
}
return result;
}
@@ -561,11 +574,15 @@ namespace ts.server {
}
getTodoComments(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[] {
throw new Error("Not Implemented Yet.");
throw new Error("Not Implemented Yet.");
}
getDocCommentTemplateAtPosition(fileName: string, position: number): TextInsertion {
throw new Error("Not Implemented Yet.");
throw new Error("Not Implemented Yet.");
}
isValidBraceCompletionAtPostion(fileName: string, position: number, openingBrace: number): boolean {
throw new Error("Not Implemented Yet.");
}
getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[] {
@@ -613,7 +630,7 @@ namespace ts.server {
throw new Error("SourceFile objects are not serializable through the server protocol.");
}
getSourceFile(fileName: string): SourceFile {
getNonBoundSourceFile(fileName: string): SourceFile {
throw new Error("SourceFile objects are not serializable through the server protocol.");
}

View File

@@ -32,8 +32,9 @@ namespace ts.server {
children: ScriptInfo[] = []; // files referenced by this file
defaultProject: Project; // project to use by default for file
fileWatcher: FileWatcher;
formatCodeOptions = ts.clone(CompilerService.defaultFormatCodeOptions);
formatCodeOptions = ts.clone(CompilerService.getDefaultFormatCodeOptions(this.host));
path: Path;
scriptKind: ScriptKind;
constructor(private host: ServerHost, public fileName: string, public content: string, public isOpen = false) {
this.path = toPath(fileName, host.getCurrentDirectory(), createGetCanonicalFileName(host.useCaseSensitiveFileNames));
@@ -81,8 +82,14 @@ namespace ts.server {
}
}
interface TimestampedResolvedModule extends ResolvedModuleWithFailedLookupLocations {
lastCheckTime: number;
interface Timestamped {
lastCheckTime?: number;
}
interface TimestampedResolvedModule extends ResolvedModuleWithFailedLookupLocations, Timestamped {
}
interface TimestampedResolvedTypeReferenceDirective extends ResolvedTypeReferenceDirectiveWithFailedLookupLocations, Timestamped {
}
export class LSHost implements ts.LanguageServiceHost {
@@ -90,60 +97,69 @@ namespace ts.server {
compilationSettings: ts.CompilerOptions;
filenameToScript: ts.FileMap<ScriptInfo>;
roots: ScriptInfo[] = [];
private resolvedModuleNames: ts.FileMap<Map<TimestampedResolvedModule>>;
private resolvedTypeReferenceDirectives: ts.FileMap<Map<TimestampedResolvedTypeReferenceDirective>>;
private moduleResolutionHost: ts.ModuleResolutionHost;
private getCanonicalFileName: (fileName: string) => string;
constructor(public host: ServerHost, public project: Project) {
this.getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames);
this.resolvedModuleNames = createFileMap<Map<TimestampedResolvedModule>>();
this.resolvedTypeReferenceDirectives = createFileMap<Map<TimestampedResolvedTypeReferenceDirective>>();
this.filenameToScript = createFileMap<ScriptInfo>();
this.moduleResolutionHost = {
fileExists: fileName => this.fileExists(fileName),
readFile: fileName => this.host.readFile(fileName)
readFile: fileName => this.host.readFile(fileName),
directoryExists: directoryName => this.host.directoryExists(directoryName)
};
}
resolveModuleNames(moduleNames: string[], containingFile: string): ResolvedModule[] {
private resolveNamesWithLocalCache<T extends Timestamped & { failedLookupLocations: string[] }, R>(
names: string[],
containingFile: string,
cache: ts.FileMap<Map<T>>,
loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost) => T,
getResult: (s: T) => R): R[] {
const path = toPath(containingFile, this.host.getCurrentDirectory(), this.getCanonicalFileName);
const currentResolutionsInFile = this.resolvedModuleNames.get(path);
const newResolutions: Map<TimestampedResolvedModule> = {};
const resolvedModules: ResolvedModule[] = [];
const currentResolutionsInFile = cache.get(path);
const newResolutions: Map<T> = {};
const resolvedModules: R[] = [];
const compilerOptions = this.getCompilationSettings();
for (const moduleName of moduleNames) {
for (const name of names) {
// check if this is a duplicate entry in the list
let resolution = lookUp(newResolutions, moduleName);
let resolution = lookUp(newResolutions, name);
if (!resolution) {
const existingResolution = currentResolutionsInFile && ts.lookUp(currentResolutionsInFile, moduleName);
const existingResolution = currentResolutionsInFile && ts.lookUp(currentResolutionsInFile, name);
if (moduleResolutionIsValid(existingResolution)) {
// ok, it is safe to use existing module resolution results
// ok, it is safe to use existing name resolution results
resolution = existingResolution;
}
else {
resolution = <TimestampedResolvedModule>resolveModuleName(moduleName, containingFile, compilerOptions, this.moduleResolutionHost);
resolution = loader(name, containingFile, compilerOptions, this.moduleResolutionHost);
resolution.lastCheckTime = Date.now();
newResolutions[moduleName] = resolution;
newResolutions[name] = resolution;
}
}
ts.Debug.assert(resolution !== undefined);
resolvedModules.push(resolution.resolvedModule);
resolvedModules.push(getResult(resolution));
}
// replace old results with a new one
this.resolvedModuleNames.set(path, newResolutions);
cache.set(path, newResolutions);
return resolvedModules;
function moduleResolutionIsValid(resolution: TimestampedResolvedModule): boolean {
function moduleResolutionIsValid(resolution: T): boolean {
if (!resolution) {
return false;
}
if (resolution.resolvedModule) {
if (getResult(resolution)) {
// TODO: consider checking failedLookupLocations
// TODO: use lastCheckTime to track expiration for module name resolution
return true;
@@ -155,6 +171,14 @@ namespace ts.server {
}
}
resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[] {
return this.resolveNamesWithLocalCache(typeDirectiveNames, containingFile, this.resolvedTypeReferenceDirectives, resolveTypeReferenceDirective, m => m.resolvedTypeReferenceDirective);
}
resolveModuleNames(moduleNames: string[], containingFile: string): ResolvedModule[] {
return this.resolveNamesWithLocalCache(moduleNames, containingFile, this.resolvedModuleNames, resolveModuleName, m => m.resolvedModule);
}
getDefaultLibFileName() {
const nodeModuleBinDir = ts.getDirectoryPath(ts.normalizePath(this.host.getExecutingFilePath()));
return ts.combinePaths(nodeModuleBinDir, ts.getDefaultLibFileName(this.compilationSettings));
@@ -171,6 +195,7 @@ namespace ts.server {
this.compilationSettings = opt;
// conservatively assume that changing compiler options might affect module resolution strategy
this.resolvedModuleNames.clear();
this.resolvedTypeReferenceDirectives.clear();
}
lineAffectsRefs(filename: string, line: number) {
@@ -191,6 +216,18 @@ namespace ts.server {
return this.roots.map(root => root.fileName);
}
getScriptKind(fileName: string) {
const info = this.getScriptInfo(fileName);
if (!info) {
return undefined;
}
if (!info.scriptKind) {
info.scriptKind = getScriptKindFromFileName(fileName);
}
return info.scriptKind;
}
getScriptVersion(filename: string) {
return this.getScriptInfo(filename).svc.latestVersion().toString();
}
@@ -207,6 +244,7 @@ namespace ts.server {
if (!info.isOpen) {
this.filenameToScript.remove(info.path);
this.resolvedModuleNames.remove(info.path);
this.resolvedTypeReferenceDirectives.remove(info.path);
}
}
@@ -234,6 +272,7 @@ namespace ts.server {
this.filenameToScript.remove(info.path);
this.roots = copyListRemovingItem(info, this.roots);
this.resolvedModuleNames.remove(info.path);
this.resolvedTypeReferenceDirectives.remove(info.path);
}
}
@@ -461,6 +500,14 @@ namespace ts.server {
return copiedList;
}
/**
* This helper funciton 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);
return projects.length > 1 ? deduplicate(result, areEqual) : result;
}
export interface ProjectServiceEventHandler {
(eventName: string, project: Project, fileName: string): void;
}
@@ -488,7 +535,7 @@ namespace ts.server {
// number becomes 0 for a watcher, then we should close it.
directoryWatchersRefCount: ts.Map<number> = {};
hostConfiguration: HostConfiguration;
timerForDetectingProjectFileListChanges: Map<NodeJS.Timer> = {};
timerForDetectingProjectFileListChanges: Map<any> = {};
constructor(public host: ServerHost, public psLogger: Logger, public eventHandler?: ProjectServiceEventHandler) {
// ts.disableIncrementalParsing = true;
@@ -497,7 +544,7 @@ namespace ts.server {
addDefaultHostConfiguration() {
this.hostConfiguration = {
formatCodeOptions: ts.clone(CompilerService.defaultFormatCodeOptions),
formatCodeOptions: ts.clone(CompilerService.getDefaultFormatCodeOptions(this.host)),
hostInfo: "Unknown host"
};
}
@@ -548,9 +595,9 @@ namespace ts.server {
startTimerForDetectingProjectFileListChanges(project: Project) {
if (this.timerForDetectingProjectFileListChanges[project.projectFilename]) {
clearTimeout(this.timerForDetectingProjectFileListChanges[project.projectFilename]);
this.host.clearTimeout(this.timerForDetectingProjectFileListChanges[project.projectFilename]);
}
this.timerForDetectingProjectFileListChanges[project.projectFilename] = setTimeout(
this.timerForDetectingProjectFileListChanges[project.projectFilename] = this.host.setTimeout(
() => this.handleProjectFileListChanges(project),
250
);
@@ -876,6 +923,7 @@ namespace ts.server {
configuredProject.updateGraph();
if (configuredProject.getSourceFile(info)) {
info.defaultProject = configuredProject;
referencingProjects.push(configuredProject);
}
}
return referencingProjects;
@@ -987,7 +1035,7 @@ namespace ts.server {
* @param filename is absolute pathname
* @param fileContent is a known version of the file content that is more up to date than the one on disk
*/
openFile(fileName: string, openedByClient: boolean, fileContent?: string) {
openFile(fileName: string, openedByClient: boolean, fileContent?: string, scriptKind?: ScriptKind) {
fileName = ts.normalizePath(fileName);
let info = ts.lookUp(this.filenameToScriptInfo, fileName);
if (!info) {
@@ -1002,6 +1050,7 @@ namespace ts.server {
}
if (content !== undefined) {
info = new ScriptInfo(this.host, fileName, content, openedByClient);
info.scriptKind = scriptKind;
info.setFormatOptions(this.getFormatCodeOptions());
this.filenameToScriptInfo[fileName] = info;
if (!info.isOpen) {
@@ -1027,10 +1076,16 @@ namespace ts.server {
// the newly opened file.
findConfigFile(searchPath: string): string {
while (true) {
const fileName = ts.combinePaths(searchPath, "tsconfig.json");
if (this.host.fileExists(fileName)) {
return fileName;
const tsconfigFileName = ts.combinePaths(searchPath, "tsconfig.json");
if (this.host.fileExists(tsconfigFileName)) {
return tsconfigFileName;
}
const jsconfigFileName = ts.combinePaths(searchPath, "jsconfig.json");
if (this.host.fileExists(jsconfigFileName)) {
return jsconfigFileName;
}
const parentPath = ts.getDirectoryPath(searchPath);
if (parentPath === searchPath) {
break;
@@ -1045,12 +1100,12 @@ namespace ts.server {
* @param filename is absolute pathname
* @param fileContent is a known version of the file content that is more up to date than the one on disk
*/
openClientFile(fileName: string, fileContent?: string) {
this.openOrUpdateConfiguredProjectForFile(fileName);
const info = this.openFile(fileName, /*openedByClient*/ true, fileContent);
openClientFile(fileName: string, fileContent?: string, scriptKind?: ScriptKind): { configFileName?: string, configFileErrors?: Diagnostic[] } {
const { configFileName, configFileErrors } = this.openOrUpdateConfiguredProjectForFile(fileName);
const info = this.openFile(fileName, /*openedByClient*/ true, fileContent, scriptKind);
this.addOpenFile(info);
this.printProjects();
return info;
return { configFileName, configFileErrors };
}
/**
@@ -1058,7 +1113,7 @@ namespace ts.server {
* we first detect if there is already a configured project created for it: if so, we re-read
* the tsconfig file content and update the project; otherwise we create a new one.
*/
openOrUpdateConfiguredProjectForFile(fileName: string) {
openOrUpdateConfiguredProjectForFile(fileName: string): { configFileName?: string, configFileErrors?: Diagnostic[] } {
const searchPath = ts.normalizePath(getDirectoryPath(fileName));
this.log("Search path: " + searchPath, "Info");
const configFileName = this.findConfigFile(searchPath);
@@ -1068,11 +1123,16 @@ namespace ts.server {
if (!project) {
const configResult = this.openConfigFile(configFileName, fileName);
if (!configResult.success) {
this.log("Error opening config file " + configFileName + " " + configResult.errorMsg);
return { configFileName, configFileErrors: configResult.errors };
}
else {
// even if opening config file was successful, it could still
// contain errors that were tolerated.
this.log("Opened configuration file " + configFileName, "Info");
this.configuredProjects.push(configResult.project);
if (configResult.errors && configResult.errors.length > 0) {
return { configFileName, configFileErrors: configResult.errors };
}
}
}
else {
@@ -1082,6 +1142,7 @@ namespace ts.server {
else {
this.log("No config files found.");
}
return {};
}
/**
@@ -1171,24 +1232,25 @@ namespace ts.server {
return undefined;
}
configFileToProjectOptions(configFilename: string): { succeeded: boolean, projectOptions?: ProjectOptions, error?: ProjectOpenResult } {
configFileToProjectOptions(configFilename: string): { succeeded: boolean, projectOptions?: ProjectOptions, errors?: Diagnostic[] } {
configFilename = ts.normalizePath(configFilename);
// file references will be relative to dirPath (or absolute)
const dirPath = ts.getDirectoryPath(configFilename);
const contents = this.host.readFile(configFilename);
const rawConfig: { config?: ProjectOptions; error?: Diagnostic; } = ts.parseConfigFileTextToJson(configFilename, contents);
if (rawConfig.error) {
return { succeeded: false, error: rawConfig.error };
return { succeeded: false, errors: [rawConfig.error] };
}
else {
const parsedCommandLine = ts.parseJsonConfigFileContent(rawConfig.config, this.host, dirPath);
const parsedCommandLine = ts.parseJsonConfigFileContent(rawConfig.config, this.host, dirPath, /*existingOptions*/ {}, configFilename);
Debug.assert(!!parsedCommandLine.fileNames);
if (parsedCommandLine.errors && (parsedCommandLine.errors.length > 0)) {
return { succeeded: false, error: { errorMsg: "tsconfig option errors" } };
return { succeeded: false, errors: parsedCommandLine.errors };
}
else if (parsedCommandLine.fileNames.length === 0) {
return { succeeded: false, error: { errorMsg: "no files found" } };
const error = createCompilerDiagnostic(Diagnostics.The_config_file_0_found_doesn_t_contain_any_source_files, configFilename);
return { succeeded: false, errors: [error] };
}
else {
const projectOptions: ProjectOptions = {
@@ -1202,20 +1264,21 @@ namespace ts.server {
}
openConfigFile(configFilename: string, clientFileName?: string): ProjectOpenResult {
const { succeeded, projectOptions, error } = this.configFileToProjectOptions(configFilename);
openConfigFile(configFilename: string, clientFileName?: string): { success: boolean, project?: Project, errors?: Diagnostic[] } {
const { succeeded, projectOptions, errors } = this.configFileToProjectOptions(configFilename);
if (!succeeded) {
return error;
return { success: false, errors };
}
else {
const project = this.createProject(configFilename, projectOptions);
let errors: Diagnostic[];
for (const rootFilename of projectOptions.files) {
if (this.host.fileExists(rootFilename)) {
const info = this.openFile(rootFilename, /*openedByClient*/ clientFileName == rootFilename);
project.addRoot(info);
}
else {
return { errorMsg: "specified file " + rootFilename + " not found" };
(errors || (errors = [])).push(createCompilerDiagnostic(Diagnostics.File_0_not_found, rootFilename));
}
}
project.finishGraph();
@@ -1244,7 +1307,7 @@ namespace ts.server {
return watchers;
}, <Map<FileWatcher>>{});
return { success: true, project: project };
return { success: true, project: project, errors };
}
}
@@ -1254,13 +1317,13 @@ namespace ts.server {
this.removeProject(project);
}
else {
const { succeeded, projectOptions, error } = this.configFileToProjectOptions(project.projectFilename);
const { succeeded, projectOptions, errors } = this.configFileToProjectOptions(project.projectFilename);
if (!succeeded) {
return error;
return errors;
}
else {
const oldFileNames = project.compilerService.host.roots.map(info => info.fileName);
const newFileNames = projectOptions.files;
const newFileNames = ts.filter(projectOptions.files, f => this.host.fileExists(f));
const fileNamesToRemove = oldFileNames.filter(f => newFileNames.indexOf(f) < 0);
const fileNamesToAdd = newFileNames.filter(f => oldFileNames.indexOf(f) < 0);
@@ -1325,6 +1388,7 @@ namespace ts.server {
else {
const defaultOpts = ts.getDefaultCompilerOptions();
defaultOpts.allowNonTsExtensions = true;
defaultOpts.allowJs = true;
this.setCompilerOptions(defaultOpts);
}
this.languageService = ts.createLanguageService(this.host, this.documentRegistry);
@@ -1337,26 +1401,29 @@ namespace ts.server {
}
isExternalModule(filename: string): boolean {
const sourceFile = this.languageService.getSourceFile(filename);
const sourceFile = this.languageService.getNonBoundSourceFile(filename);
return ts.isExternalModule(sourceFile);
}
static defaultFormatCodeOptions: ts.FormatCodeOptions = {
IndentSize: 4,
TabSize: 4,
NewLineCharacter: ts.sys ? ts.sys.newLine : "\n",
ConvertTabsToSpaces: true,
IndentStyle: ts.IndentStyle.Smart,
InsertSpaceAfterCommaDelimiter: true,
InsertSpaceAfterSemicolonInForStatements: true,
InsertSpaceBeforeAndAfterBinaryOperators: true,
InsertSpaceAfterKeywordsInControlFlowStatements: true,
InsertSpaceAfterFunctionKeywordForAnonymousFunctions: false,
InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false,
InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false,
PlaceOpenBraceOnNewLineForFunctions: false,
PlaceOpenBraceOnNewLineForControlBlocks: false,
};
static getDefaultFormatCodeOptions(host: ServerHost): ts.FormatCodeOptions {
return ts.clone({
IndentSize: 4,
TabSize: 4,
NewLineCharacter: host.newLine || "\n",
ConvertTabsToSpaces: true,
IndentStyle: ts.IndentStyle.Smart,
InsertSpaceAfterCommaDelimiter: true,
InsertSpaceAfterSemicolonInForStatements: true,
InsertSpaceBeforeAndAfterBinaryOperators: true,
InsertSpaceAfterKeywordsInControlFlowStatements: true,
InsertSpaceAfterFunctionKeywordForAnonymousFunctions: false,
InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false,
InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false,
InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false,
PlaceOpenBraceOnNewLineForFunctions: false,
PlaceOpenBraceOnNewLineForControlBlocks: false,
});
}
}
export interface LineCollection {
@@ -1651,7 +1718,12 @@ namespace ts.server {
}
reloadFromFile(filename: string, cb?: () => any) {
const content = this.host.readFile(filename);
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);
if (cb)
cb();

View File

@@ -1,54 +1,54 @@
/**
* Declaration module describing the TypeScript Server protocol
/**
* Declaration module describing the TypeScript Server protocol
*/
declare namespace ts.server.protocol {
/**
* A TypeScript Server message
/**
* A TypeScript Server message
*/
export interface Message {
/**
* Sequence number of the message
/**
* Sequence number of the message
*/
seq: number;
/**
* One of "request", "response", or "event"
* One of "request", "response", or "event"
*/
type: string;
}
/**
* Client-initiated request message
/**
* Client-initiated request message
*/
export interface Request extends Message {
/**
* The command to execute
* The command to execute
*/
command: string;
/**
* Object containing arguments for the command
/**
* Object containing arguments for the command
*/
arguments?: any;
}
/**
* Request to reload the project structure for all the opened files
* Request to reload the project structure for all the opened files
*/
export interface ReloadProjectsRequest extends Message {
}
/**
* Server-initiated event message
/**
* Server-initiated event message
*/
export interface Event extends Message {
/**
* Name of event
/**
* Name of event
*/
event: string;
/**
* Event-specific information
/**
* Event-specific information
*/
body?: any;
}
@@ -62,18 +62,18 @@ declare namespace ts.server.protocol {
*/
request_seq: number;
/**
* Outcome of the request.
/**
* Outcome of the request.
*/
success: boolean;
/**
/**
* The command requested.
*/
command: string;
/**
* Contains error message if success === false.
/**
* Contains error message if success === false.
*/
message?: string;
@@ -83,7 +83,7 @@ declare namespace ts.server.protocol {
body?: any;
}
/**
/**
* Arguments for FileRequest messages.
*/
export interface FileRequestArgs {
@@ -93,7 +93,7 @@ declare namespace ts.server.protocol {
file: string;
}
/**
/**
* Arguments for ProjectInfoRequest request.
*/
export interface ProjectInfoRequestArgs extends FileRequestArgs {
@@ -110,7 +110,7 @@ declare namespace ts.server.protocol {
arguments: ProjectInfoRequestArgs;
}
/**
/**
* Response message body for "projectInfo" request
*/
export interface ProjectInfo {
@@ -125,7 +125,7 @@ declare namespace ts.server.protocol {
fileNames?: string[];
}
/**
/**
* Response message for "projectInfo" request
*/
export interface ProjectInfoResponse extends Response {
@@ -144,12 +144,12 @@ declare namespace ts.server.protocol {
* (file, line, character offset), where line and character offset are 1-based.
*/
export interface FileLocationRequestArgs extends FileRequestArgs {
/**
/**
* The line number for the request (1-based).
*/
line: number;
/**
/**
* The character offset (on the line) for the request (1-based).
*/
offset: number;
@@ -216,7 +216,7 @@ declare namespace ts.server.protocol {
* Object found in response messages defining a span of text in a specific source file.
*/
export interface FileSpan extends TextSpan {
/**
/**
* File containing text span.
*/
file: string;
@@ -300,14 +300,14 @@ declare namespace ts.server.protocol {
*/
lineText: string;
/**
/**
* True if reference is a write location, false otherwise.
*/
isWriteAccess: boolean;
}
/**
* The body of a "references" response message.
* The body of a "references" response message.
*/
export interface ReferencesResponseBody {
/**
@@ -325,7 +325,7 @@ declare namespace ts.server.protocol {
*/
symbolStartOffset: number;
/**
/**
* The full display name of the symbol.
*/
symbolDisplayString: string;
@@ -355,7 +355,7 @@ declare namespace ts.server.protocol {
}
/**
* Information about the item to be renamed.
* Information about the item to be renamed.
*/
export interface RenameInfo {
/**
@@ -373,7 +373,7 @@ declare namespace ts.server.protocol {
*/
displayName: string;
/**
/**
* Full display name of item to be renamed.
*/
fullDisplayName: string;
@@ -383,7 +383,7 @@ declare namespace ts.server.protocol {
*/
kind: string;
/**
/**
* Optional modifiers for the kind (such as 'public').
*/
kindModifiers: string;
@@ -444,7 +444,7 @@ declare namespace ts.server.protocol {
/** Defines space handling after a comma delimiter. Default value is true. */
insertSpaceAfterCommaDelimiter?: boolean;
/** Defines space handling after a semicolon in a for statemen. Default value is true */
/** Defines space handling after a semicolon in a for statement. Default value is true */
insertSpaceAfterSemicolonInForStatements?: boolean;
/** Defines space handling after a binary operator. Default value is true. */
@@ -469,7 +469,7 @@ declare namespace ts.server.protocol {
placeOpenBraceOnNewLineForControlBlocks?: boolean;
/** Index operator */
[key: string] : string | number | boolean;
[key: string]: string | number | boolean;
}
/**
@@ -477,7 +477,7 @@ declare namespace ts.server.protocol {
*/
export interface ConfigureRequestArguments {
/**
/**
* Information about the host, for example 'Emacs 24.4' or
* 'Sublime Text version 3075'
*/
@@ -495,7 +495,7 @@ declare namespace ts.server.protocol {
}
/**
* Configure request; value of command field is "configure". Specifies
* Configure request; value of command field is "configure". Specifies
* host information, such as host type, tab size, and indent size.
*/
export interface ConfigureRequest extends Request {
@@ -514,10 +514,15 @@ declare namespace ts.server.protocol {
*/
export interface OpenRequestArgs extends FileRequestArgs {
/**
* Used when a version of the file content is known to be more up to date than the one on disk.
* Then the known content will be used upon opening instead of the disk copy
* Used when a version of the file content is known to be more up to date than the one on disk.
* Then the known content will be used upon opening instead of the disk copy
*/
fileContent?: string;
/**
* Used to specify the script kind of the file explicitly. It could be one of the following:
* "TS", "JS", "TSX", "JSX"
*/
scriptKindName?: "TS" | "JS" | "TSX" | "JSX";
}
/**
@@ -751,7 +756,7 @@ declare namespace ts.server.protocol {
* Optional modifiers for the kind (such as 'public').
*/
kindModifiers: string;
/**
/**
* A string that is used for comparing completion items so that they can be ordered. This
* is often the same as the name but may be different in certain circumstances.
*/
@@ -794,7 +799,7 @@ declare namespace ts.server.protocol {
}
/**
* Signature help information for a single parameter
* Signature help information for a single parameter
*/
export interface SignatureHelpParameter {
@@ -814,18 +819,18 @@ declare namespace ts.server.protocol {
displayParts: SymbolDisplayPart[];
/**
* Whether the parameter is optional or not.
* Whether the parameter is optional or not.
*/
isOptional: boolean;
}
/**
* Represents a single signature to show in signature help.
* Represents a single signature to show in signature help.
*/
export interface SignatureHelpItem {
/**
* Whether the signature accepts a variable number of arguments.
* Whether the signature accepts a variable number of arguments.
*/
isVariadic: boolean;
@@ -835,7 +840,7 @@ declare namespace ts.server.protocol {
prefixDisplayParts: SymbolDisplayPart[];
/**
* The suffix disaply parts.
* The suffix display parts.
*/
suffixDisplayParts: SymbolDisplayPart[];
@@ -845,7 +850,7 @@ declare namespace ts.server.protocol {
separatorDisplayParts: SymbolDisplayPart[];
/**
* The signature helps items for the parameters.
* The signature helps items for the parameters.
*/
parameters: SignatureHelpParameter[];
@@ -861,17 +866,17 @@ declare namespace ts.server.protocol {
export interface SignatureHelpItems {
/**
* The signature help items.
* The signature help items.
*/
items: SignatureHelpItem[];
/**
* The span for which signature help should appear on a signature
* The span for which signature help should appear on a signature
*/
applicableSpan: TextSpan;
/**
* The item selected in the set of available help items.
* The item selected in the set of available help items.
*/
selectedItemIndex: number;
@@ -895,7 +900,7 @@ declare namespace ts.server.protocol {
/**
* Signature help request; value of command field is "signatureHelp".
* Given a file location (file, line, col), return the signature
* Given a file location (file, line, col), return the signature
* help.
*/
export interface SignatureHelpRequest extends FileLocationRequest {
@@ -903,7 +908,7 @@ declare namespace ts.server.protocol {
}
/**
* Repsonse object for a SignatureHelpRequest.
* Response object for a SignatureHelpRequest.
*/
export interface SignatureHelpResponse extends Response {
body?: SignatureHelpItems;
@@ -926,8 +931,8 @@ declare namespace ts.server.protocol {
}
/**
* GeterrForProjectRequest request; value of command field is
* "geterrForProject". It works similarly with 'Geterr', only
* GeterrForProjectRequest request; value of command field is
* "geterrForProject". It works similarly with 'Geterr', only
* it request for every file in this project.
*/
export interface GeterrForProjectRequest extends Request {
@@ -970,7 +975,7 @@ declare namespace ts.server.protocol {
*/
export interface Diagnostic {
/**
* Starting file location at which text appies.
* Starting file location at which text applies.
*/
start: Location;
@@ -997,7 +1002,7 @@ declare namespace ts.server.protocol {
diagnostics: Diagnostic[];
}
/**
/**
* Event message for "syntaxDiag" and "semanticDiag" event types.
* These events provide syntactic and semantic errors for a file.
*/
@@ -1005,6 +1010,32 @@ declare namespace ts.server.protocol {
body?: DiagnosticEventBody;
}
export interface ConfigFileDiagnosticEventBody {
/**
* The file which trigged the searching and error-checking of the config file
*/
triggerFile: string;
/**
* The name of the found config file.
*/
configFile: string;
/**
* An arry of diagnostic information items for the found config file.
*/
diagnostics: Diagnostic[];
}
/**
* Event message for "configFileDiag" event type.
* This event provides errors for a found config file.
*/
export interface ConfigFileDiagnosticEvent extends Event {
body?: ConfigFileDiagnosticEventBody;
event: "configFileDiag";
}
/**
* Arguments for reload request.
*/
@@ -1033,7 +1064,7 @@ declare namespace ts.server.protocol {
export interface ReloadResponse extends Response {
}
/**
/**
* Arguments for saveto request.
*/
export interface SavetoRequestArgs extends FileRequestArgs {
@@ -1109,7 +1140,7 @@ declare namespace ts.server.protocol {
*/
kindModifiers?: string;
/**
/**
* The file in which the symbol is found.
*/
file: string;
@@ -1156,7 +1187,7 @@ declare namespace ts.server.protocol {
/**
* Change request message; value of command field is "change".
* Update the server's view of the file named by argument 'file'.
* Update the server's view of the file named by argument 'file'.
* Server does not currently send a response to a change request.
*/
export interface ChangeRequest extends FileLocationRequest {
@@ -1179,7 +1210,7 @@ declare namespace ts.server.protocol {
}
/**
* NavBar itesm request; value of command field is "navbar".
* NavBar items request; value of command field is "navbar".
* Return response giving the list of navigation bar entries
* extracted from the requested file.
*/

View File

@@ -1,7 +1,7 @@
/// <reference path="node.d.ts" />
/// <reference path="session.ts" />
// used in fs.writeSync
/* tslint:disable:no-null */
/* tslint:disable:no-null-keyword */
namespace ts.server {
const readline: NodeJS.ReadLine = require("readline");
@@ -153,6 +153,98 @@ namespace ts.server {
// This places log file in the directory containing editorServices.js
// TODO: check that this location is writable
// average async stat takes about 30 microseconds
// set chunk size to do 30 files in < 1 millisecond
function createPollingWatchedFileSet(interval = 2500, chunkSize = 30) {
let watchedFiles: WatchedFile[] = [];
let nextFileToCheck = 0;
let watchTimer: any;
function getModifiedTime(fileName: string): Date {
return fs.statSync(fileName).mtime;
}
function poll(checkedIndex: number) {
const watchedFile = watchedFiles[checkedIndex];
if (!watchedFile) {
return;
}
fs.stat(watchedFile.fileName, (err: any, stats: any) => {
if (err) {
watchedFile.callback(watchedFile.fileName);
}
else if (watchedFile.mtime.getTime() !== stats.mtime.getTime()) {
watchedFile.mtime = getModifiedTime(watchedFile.fileName);
watchedFile.callback(watchedFile.fileName, watchedFile.mtime.getTime() === 0);
}
});
}
// this implementation uses polling and
// stat due to inconsistencies of fs.watch
// and efficiency of stat on modern filesystems
function startWatchTimer() {
watchTimer = setInterval(() => {
let count = 0;
let nextToCheck = nextFileToCheck;
let firstCheck = -1;
while ((count < chunkSize) && (nextToCheck !== firstCheck)) {
poll(nextToCheck);
if (firstCheck < 0) {
firstCheck = nextToCheck;
}
nextToCheck++;
if (nextToCheck === watchedFiles.length) {
nextToCheck = 0;
}
count++;
}
nextFileToCheck = nextToCheck;
}, interval);
}
function addFile(fileName: string, callback: FileWatcherCallback): WatchedFile {
const file: WatchedFile = {
fileName,
callback,
mtime: getModifiedTime(fileName)
};
watchedFiles.push(file);
if (watchedFiles.length === 1) {
startWatchTimer();
}
return file;
}
function removeFile(file: WatchedFile) {
watchedFiles = copyListRemovingItem(file, watchedFiles);
}
return {
getModifiedTime: getModifiedTime,
poll: poll,
startWatchTimer: startWatchTimer,
addFile: addFile,
removeFile: removeFile
};
}
// REVIEW: for now this implementation uses polling.
// The advantage of polling is that it works reliably
// on all os and with network mounted files.
// For 90 referenced files, the average time to detect
// changes is 2*msInterval (by default 5 seconds).
// The overhead of this is .04 percent (1/2500) with
// average pause of < 1 millisecond (and max
// pause less than 1.5 milliseconds); question is
// do we anticipate reference sets in the 100s and
// do we care about waiting 10-20 seconds to detect
// changes for large reference sets? If so, do we want
// to increase the chunk size or decrease the interval
// time dynamically to match the large reference set?
const pollingWatchedFileSet = createPollingWatchedFileSet();
const logger = createLoggerFromEnv();
const pending: string[] = [];
@@ -174,10 +266,21 @@ namespace ts.server {
}
}
// Override sys.write because fs.writeSync is not reliable on Node 4
ts.sys.write = (s: string) => writeMessage(s);
const sys = <ServerHost>ts.sys;
const ioSession = new IOSession(ts.sys, logger);
// Override sys.write because fs.writeSync is not reliable on Node 4
sys.write = (s: string) => writeMessage(s);
sys.watchFile = (fileName, callback) => {
const watchedFile = pollingWatchedFileSet.addFile(fileName, callback);
return {
close: () => pollingWatchedFileSet.removeFile(watchedFile)
};
};
sys.setTimeout = setTimeout;
sys.clearTimeout = clearTimeout;
const ioSession = new IOSession(sys, logger);
process.on("uncaughtException", function(err: Error) {
ioSession.logError(err, "unknown");
});

View File

@@ -68,7 +68,7 @@ namespace ts.server {
}
}
function formatDiag(fileName: string, project: Project, diag: ts.Diagnostic) {
function formatDiag(fileName: string, project: Project, diag: ts.Diagnostic): protocol.Diagnostic {
return {
start: project.compilerService.host.positionToLineOffset(fileName, diag.start),
end: project.compilerService.host.positionToLineOffset(fileName, diag.start + diag.length),
@@ -76,6 +76,14 @@ namespace ts.server {
};
}
function formatConfigFileDiag(diag: ts.Diagnostic): protocol.Diagnostic {
return {
start: undefined,
end: undefined,
text: ts.flattenDiagnosticMessageText(diag.messageText, "\n")
};
}
export interface PendingErrorCheck {
fileName: string;
project: Project;
@@ -125,6 +133,8 @@ namespace ts.server {
}
export interface ServerHost extends ts.System {
setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): any;
clearTimeout(timeoutId: any): void;
}
export class Session {
@@ -141,8 +151,8 @@ namespace ts.server {
) {
this.projectService =
new ProjectService(host, logger, (eventName, project, fileName) => {
this.handleEvent(eventName, project, fileName);
});
this.handleEvent(eventName, project, fileName);
});
}
private handleEvent(eventName: string, project: Project, fileName: string) {
@@ -178,6 +188,21 @@ namespace ts.server {
"\r\n\r\n" + json);
}
public configFileDiagnosticEvent(triggerFile: string, configFile: string, diagnostics: ts.Diagnostic[]) {
const bakedDiags = ts.map(diagnostics, formatConfigFileDiag);
const ev: protocol.ConfigFileDiagnosticEvent = {
seq: 0,
type: "event",
event: "configFileDiag",
body: {
triggerFile,
configFile,
diagnostics: bakedDiags
}
};
this.send(ev);
}
public event(info: any, eventName: string) {
const ev: protocol.Event = {
seq: 0,
@@ -412,14 +437,17 @@ namespace ts.server {
private getRenameLocations(line: number, offset: number, fileName: string, findInComments: boolean, findInStrings: boolean): protocol.RenameResponseBody {
const file = ts.normalizePath(fileName);
const project = this.projectService.getProjectForFile(file);
if (!project) {
const info = this.projectService.getScriptInfo(file);
const projects = this.projectService.findReferencingProjects(info);
if (!projects.length) {
throw Errors.NoProject;
}
const compilerService = project.compilerService;
const position = compilerService.host.lineOffsetToPosition(file, line, offset);
const renameInfo = compilerService.languageService.getRenameInfo(file, position);
const defaultProject = projects[0];
// The rename info should be the same for every project
const defaultProjectCompilerService = defaultProject.compilerService;
const position = defaultProjectCompilerService.host.lineOffsetToPosition(file, line, offset);
const renameInfo = defaultProjectCompilerService.languageService.getRenameInfo(file, position);
if (!renameInfo) {
return undefined;
}
@@ -431,16 +459,43 @@ namespace ts.server {
};
}
const renameLocations = compilerService.languageService.findRenameLocations(file, position, findInStrings, findInComments);
if (!renameLocations) {
return undefined;
}
const fileSpans = combineProjectOutput(
projects,
(project: Project) => {
const compilerService = project.compilerService;
const renameLocations = compilerService.languageService.findRenameLocations(file, position, findInStrings, findInComments);
if (!renameLocations) {
return [];
}
const bakedRenameLocs = renameLocations.map(location => (<protocol.FileSpan>{
file: location.fileName,
start: compilerService.host.positionToLineOffset(location.fileName, location.textSpan.start),
end: compilerService.host.positionToLineOffset(location.fileName, ts.textSpanEnd(location.textSpan)),
})).sort((a, b) => {
return renameLocations.map(location => (<protocol.FileSpan>{
file: location.fileName,
start: compilerService.host.positionToLineOffset(location.fileName, location.textSpan.start),
end: compilerService.host.positionToLineOffset(location.fileName, ts.textSpanEnd(location.textSpan)),
}));
},
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) => {
let curFileAccum: protocol.SpanGroup;
if (accum.length > 0) {
curFileAccum = accum[accum.length - 1];
if (curFileAccum.file !== cur.file) {
curFileAccum = undefined;
}
}
if (!curFileAccum) {
curFileAccum = { file: cur.file, locs: [] };
accum.push(curFileAccum);
}
curFileAccum.locs.push({ start: cur.start, end: cur.end });
return accum;
}, []);
return { info: renameInfo, locs };
function compareRenameLocation(a: protocol.FileSpan, b: protocol.FileSpan) {
if (a.file < b.file) {
return -1;
}
@@ -459,79 +514,82 @@ namespace ts.server {
return b.start.offset - a.start.offset;
}
}
}).reduce<protocol.SpanGroup[]>((accum: protocol.SpanGroup[], cur: protocol.FileSpan) => {
let curFileAccum: protocol.SpanGroup;
if (accum.length > 0) {
curFileAccum = accum[accum.length - 1];
if (curFileAccum.file != cur.file) {
curFileAccum = undefined;
}
}
if (!curFileAccum) {
curFileAccum = { file: cur.file, locs: [] };
accum.push(curFileAccum);
}
curFileAccum.locs.push({ start: cur.start, end: cur.end });
return accum;
}, []);
return { info: renameInfo, locs: bakedRenameLocs };
}
}
private getReferences(line: number, offset: number, fileName: string): protocol.ReferencesResponseBody {
// TODO: get all projects for this file; report refs for all projects deleting duplicates
// can avoid duplicates by eliminating same ref file from subsequent projects
const file = ts.normalizePath(fileName);
const project = this.projectService.getProjectForFile(file);
if (!project) {
const info = this.projectService.getScriptInfo(file);
const projects = this.projectService.findReferencingProjects(info);
if (!projects.length) {
throw Errors.NoProject;
}
const compilerService = project.compilerService;
const position = compilerService.host.lineOffsetToPosition(file, line, offset);
const references = compilerService.languageService.getReferencesAtPosition(file, position);
if (!references) {
return undefined;
}
const nameInfo = compilerService.languageService.getQuickInfoAtPosition(file, position);
const defaultProject = projects[0];
const position = defaultProject.compilerService.host.lineOffsetToPosition(file, line, offset);
const nameInfo = defaultProject.compilerService.languageService.getQuickInfoAtPosition(file, position);
if (!nameInfo) {
return undefined;
}
const displayString = ts.displayPartsToString(nameInfo.displayParts);
const nameSpan = nameInfo.textSpan;
const nameColStart = compilerService.host.positionToLineOffset(file, nameSpan.start).offset;
const nameText = compilerService.host.getScriptSnapshot(file).getText(nameSpan.start, ts.textSpanEnd(nameSpan));
const bakedRefs: protocol.ReferencesResponseItem[] = references.map(ref => {
const start = compilerService.host.positionToLineOffset(ref.fileName, ref.textSpan.start);
const refLineSpan = compilerService.host.lineToTextSpan(ref.fileName, start.line - 1);
const snap = compilerService.host.getScriptSnapshot(ref.fileName);
const lineText = snap.getText(refLineSpan.start, ts.textSpanEnd(refLineSpan)).replace(/\r|\n/g, "");
return {
file: ref.fileName,
start: start,
lineText: lineText,
end: compilerService.host.positionToLineOffset(ref.fileName, ts.textSpanEnd(ref.textSpan)),
isWriteAccess: ref.isWriteAccess
};
}).sort(compareFileStart);
const nameColStart = defaultProject.compilerService.host.positionToLineOffset(file, nameSpan.start).offset;
const nameText = defaultProject.compilerService.host.getScriptSnapshot(file).getText(nameSpan.start, ts.textSpanEnd(nameSpan));
const refs = combineProjectOutput<protocol.ReferencesResponseItem>(
projects,
(project: Project) => {
const compilerService = project.compilerService;
const references = compilerService.languageService.getReferencesAtPosition(file, position);
if (!references) {
return [];
}
return references.map(ref => {
const start = compilerService.host.positionToLineOffset(ref.fileName, ref.textSpan.start);
const refLineSpan = compilerService.host.lineToTextSpan(ref.fileName, start.line - 1);
const snap = compilerService.host.getScriptSnapshot(ref.fileName);
const lineText = snap.getText(refLineSpan.start, ts.textSpanEnd(refLineSpan)).replace(/\r|\n/g, "");
return {
file: ref.fileName,
start: start,
lineText: lineText,
end: compilerService.host.positionToLineOffset(ref.fileName, ts.textSpanEnd(ref.textSpan)),
isWriteAccess: ref.isWriteAccess
};
});
},
compareFileStart,
areReferencesResponseItemsForTheSameLocation
);
return {
refs: bakedRefs,
refs,
symbolName: nameText,
symbolStartOffset: nameColStart,
symbolDisplayString: displayString
};
function areReferencesResponseItemsForTheSameLocation(a: protocol.ReferencesResponseItem, b: protocol.ReferencesResponseItem) {
if (a && b) {
return a.file === b.file &&
a.start === b.start &&
a.end === b.end;
}
return false;
}
}
/**
* @param fileName is the name of the file to be opened
* @param fileContent is a version of the file content that is known to be more up to date than the one on disk
*/
private openClientFile(fileName: string, fileContent?: string) {
private openClientFile(fileName: string, fileContent?: string, scriptKind?: ScriptKind) {
const file = ts.normalizePath(fileName);
this.projectService.openClientFile(file, fileContent);
const { configFileName, configFileErrors } = this.projectService.openClientFile(file, fileContent, scriptKind);
if (configFileErrors) {
this.configFileDiagnosticEvent(fileName, configFileName, configFileErrors);
}
}
private getQuickInfo(line: number, offset: number, fileName: string): protocol.QuickInfoResponseBody {
@@ -603,7 +661,7 @@ namespace ts.server {
// Check whether we should auto-indent. This will be when
// the position is on a line containing only whitespace.
// This should leave the edits returned from
// getFormattingEditsAfterKeytroke either empty or pertaining
// getFormattingEditsAfterKeystroke either empty or pertaining
// only to the previous line. If all this is true, then
// add edits necessary to properly indent the current line.
if ((key == "\n") && ((!edits) || (edits.length === 0) || allEditsBeforePos(edits, position))) {
@@ -617,7 +675,7 @@ namespace ts.server {
const editorOptions: ts.EditorOptions = {
IndentSize: formatOptions.IndentSize,
TabSize: formatOptions.TabSize,
NewLineCharacter: "\n",
NewLineCharacter: formatOptions.NewLineCharacter,
ConvertTabsToSpaces: formatOptions.ConvertTabsToSpaces,
IndentStyle: ts.IndentStyle.Smart,
};
@@ -792,7 +850,9 @@ namespace ts.server {
}
private closeClientFile(fileName: string) {
if (!fileName) { return; }
if (!fileName) {
return;
}
const file = ts.normalizePath(fileName);
this.projectService.closeClientFile(file);
}
@@ -834,41 +894,60 @@ namespace ts.server {
private getNavigateToItems(searchValue: string, fileName: string, maxResultCount?: number): protocol.NavtoItem[] {
const file = ts.normalizePath(fileName);
const project = this.projectService.getProjectForFile(file);
if (!project) {
const info = this.projectService.getScriptInfo(file);
const projects = this.projectService.findReferencingProjects(info);
const defaultProject = projects[0];
if (!defaultProject) {
throw Errors.NoProject;
}
const compilerService = project.compilerService;
const navItems = compilerService.languageService.getNavigateToItems(searchValue, maxResultCount);
if (!navItems) {
return undefined;
}
const allNavToItems = combineProjectOutput(
projects,
(project: Project) => {
const compilerService = project.compilerService;
const navItems = compilerService.languageService.getNavigateToItems(searchValue, maxResultCount);
if (!navItems) {
return [];
}
return navItems.map((navItem) => {
const start = compilerService.host.positionToLineOffset(navItem.fileName, navItem.textSpan.start);
const end = compilerService.host.positionToLineOffset(navItem.fileName, ts.textSpanEnd(navItem.textSpan));
const bakedItem: protocol.NavtoItem = {
name: navItem.name,
kind: navItem.kind,
file: navItem.fileName,
start: start,
end: end,
};
if (navItem.kindModifiers && (navItem.kindModifiers != "")) {
bakedItem.kindModifiers = navItem.kindModifiers;
return navItems.map((navItem) => {
const start = compilerService.host.positionToLineOffset(navItem.fileName, navItem.textSpan.start);
const end = compilerService.host.positionToLineOffset(navItem.fileName, ts.textSpanEnd(navItem.textSpan));
const bakedItem: protocol.NavtoItem = {
name: navItem.name,
kind: navItem.kind,
file: navItem.fileName,
start: start,
end: end,
};
if (navItem.kindModifiers && (navItem.kindModifiers !== "")) {
bakedItem.kindModifiers = navItem.kindModifiers;
}
if (navItem.matchKind !== "none") {
bakedItem.matchKind = navItem.matchKind;
}
if (navItem.containerName && (navItem.containerName.length > 0)) {
bakedItem.containerName = navItem.containerName;
}
if (navItem.containerKind && (navItem.containerKind.length > 0)) {
bakedItem.containerKind = navItem.containerKind;
}
return bakedItem;
});
},
/*comparer*/ undefined,
areNavToItemsForTheSameLocation
);
return allNavToItems;
function areNavToItemsForTheSameLocation(a: protocol.NavtoItem, b: protocol.NavtoItem) {
if (a && b) {
return a.file === b.file &&
a.start === b.start &&
a.end === b.end;
}
if (navItem.matchKind !== "none") {
bakedItem.matchKind = navItem.matchKind;
}
if (navItem.containerName && (navItem.containerName.length > 0)) {
bakedItem.containerName = navItem.containerName;
}
if (navItem.containerKind && (navItem.containerKind.length > 0)) {
bakedItem.containerKind = navItem.containerKind;
}
return bakedItem;
});
return false;
}
}
private getBraceMatching(line: number, offset: number, fileName: string): protocol.TextSpan[] {
@@ -942,129 +1021,146 @@ namespace ts.server {
exit() {
}
private handlers: Map<(request: protocol.Request) => {response?: any, responseRequired?: boolean}> = {
private handlers: Map<(request: protocol.Request) => { response?: any, responseRequired?: boolean }> = {
[CommandNames.Exit]: () => {
this.exit();
return { responseRequired: false};
return { responseRequired: false };
},
[CommandNames.Definition]: (request: protocol.Request) => {
const defArgs = <protocol.FileLocationRequestArgs>request.arguments;
return {response: this.getDefinition(defArgs.line, defArgs.offset, defArgs.file), responseRequired: true};
return { response: this.getDefinition(defArgs.line, defArgs.offset, defArgs.file), responseRequired: true };
},
[CommandNames.TypeDefinition]: (request: protocol.Request) => {
const defArgs = <protocol.FileLocationRequestArgs>request.arguments;
return {response: this.getTypeDefinition(defArgs.line, defArgs.offset, defArgs.file), responseRequired: true};
return { response: this.getTypeDefinition(defArgs.line, defArgs.offset, defArgs.file), responseRequired: true };
},
[CommandNames.References]: (request: protocol.Request) => {
const defArgs = <protocol.FileLocationRequestArgs>request.arguments;
return {response: this.getReferences(defArgs.line, defArgs.offset, defArgs.file), responseRequired: true};
return { response: this.getReferences(defArgs.line, defArgs.offset, defArgs.file), responseRequired: true };
},
[CommandNames.Rename]: (request: protocol.Request) => {
const renameArgs = <protocol.RenameRequestArgs>request.arguments;
return {response: this.getRenameLocations(renameArgs.line, renameArgs.offset, renameArgs.file, renameArgs.findInComments, renameArgs.findInStrings), responseRequired: true};
return { response: this.getRenameLocations(renameArgs.line, renameArgs.offset, renameArgs.file, renameArgs.findInComments, renameArgs.findInStrings), responseRequired: true };
},
[CommandNames.Open]: (request: protocol.Request) => {
const openArgs = <protocol.OpenRequestArgs>request.arguments;
this.openClientFile(openArgs.file, openArgs.fileContent);
return {responseRequired: false};
let scriptKind: ScriptKind;
switch (openArgs.scriptKindName) {
case "TS":
scriptKind = ScriptKind.TS;
break;
case "JS":
scriptKind = ScriptKind.JS;
break;
case "TSX":
scriptKind = ScriptKind.TSX;
break;
case "JSX":
scriptKind = ScriptKind.JSX;
break;
}
this.openClientFile(openArgs.file, openArgs.fileContent, scriptKind);
return { responseRequired: false };
},
[CommandNames.Quickinfo]: (request: protocol.Request) => {
const quickinfoArgs = <protocol.FileLocationRequestArgs>request.arguments;
return {response: this.getQuickInfo(quickinfoArgs.line, quickinfoArgs.offset, quickinfoArgs.file), responseRequired: true};
return { response: this.getQuickInfo(quickinfoArgs.line, quickinfoArgs.offset, quickinfoArgs.file), responseRequired: true };
},
[CommandNames.Format]: (request: protocol.Request) => {
const formatArgs = <protocol.FormatRequestArgs>request.arguments;
return {response: this.getFormattingEditsForRange(formatArgs.line, formatArgs.offset, formatArgs.endLine, formatArgs.endOffset, formatArgs.file), responseRequired: true};
return { response: this.getFormattingEditsForRange(formatArgs.line, formatArgs.offset, formatArgs.endLine, formatArgs.endOffset, formatArgs.file), responseRequired: true };
},
[CommandNames.Formatonkey]: (request: protocol.Request) => {
const formatOnKeyArgs = <protocol.FormatOnKeyRequestArgs>request.arguments;
return {response: this.getFormattingEditsAfterKeystroke(formatOnKeyArgs.line, formatOnKeyArgs.offset, formatOnKeyArgs.key, formatOnKeyArgs.file), responseRequired: true};
return { response: this.getFormattingEditsAfterKeystroke(formatOnKeyArgs.line, formatOnKeyArgs.offset, formatOnKeyArgs.key, formatOnKeyArgs.file), responseRequired: true };
},
[CommandNames.Completions]: (request: protocol.Request) => {
const completionsArgs = <protocol.CompletionsRequestArgs>request.arguments;
return {response: this.getCompletions(completionsArgs.line, completionsArgs.offset, completionsArgs.prefix, completionsArgs.file), responseRequired: true};
return { response: this.getCompletions(completionsArgs.line, completionsArgs.offset, completionsArgs.prefix, completionsArgs.file), responseRequired: true };
},
[CommandNames.CompletionDetails]: (request: protocol.Request) => {
const completionDetailsArgs = <protocol.CompletionDetailsRequestArgs>request.arguments;
return {response: this.getCompletionEntryDetails(completionDetailsArgs.line, completionDetailsArgs.offset,
completionDetailsArgs.entryNames, completionDetailsArgs.file), responseRequired: true};
return {
response: this.getCompletionEntryDetails(completionDetailsArgs.line, completionDetailsArgs.offset,
completionDetailsArgs.entryNames, completionDetailsArgs.file), responseRequired: true
};
},
[CommandNames.SignatureHelp]: (request: protocol.Request) => {
const signatureHelpArgs = <protocol.SignatureHelpRequestArgs>request.arguments;
return {response: this.getSignatureHelpItems(signatureHelpArgs.line, signatureHelpArgs.offset, signatureHelpArgs.file), responseRequired: true};
return { response: this.getSignatureHelpItems(signatureHelpArgs.line, signatureHelpArgs.offset, signatureHelpArgs.file), responseRequired: true };
},
[CommandNames.Geterr]: (request: protocol.Request) => {
const geterrArgs = <protocol.GeterrRequestArgs>request.arguments;
return {response: this.getDiagnostics(geterrArgs.delay, geterrArgs.files), responseRequired: false};
return { response: this.getDiagnostics(geterrArgs.delay, geterrArgs.files), responseRequired: false };
},
[CommandNames.GeterrForProject]: (request: protocol.Request) => {
const { file, delay } = <protocol.GeterrForProjectRequestArgs>request.arguments;
return {response: this.getDiagnosticsForProject(delay, file), responseRequired: false};
return { response: this.getDiagnosticsForProject(delay, file), responseRequired: false };
},
[CommandNames.Change]: (request: protocol.Request) => {
const changeArgs = <protocol.ChangeRequestArgs>request.arguments;
this.change(changeArgs.line, changeArgs.offset, changeArgs.endLine, changeArgs.endOffset,
changeArgs.insertString, changeArgs.file);
return {responseRequired: false};
changeArgs.insertString, changeArgs.file);
return { responseRequired: false };
},
[CommandNames.Configure]: (request: protocol.Request) => {
const configureArgs = <protocol.ConfigureRequestArguments>request.arguments;
this.projectService.setHostConfiguration(configureArgs);
this.output(undefined, CommandNames.Configure, request.seq);
return {responseRequired: false};
return { responseRequired: false };
},
[CommandNames.Reload]: (request: protocol.Request) => {
const reloadArgs = <protocol.ReloadRequestArgs>request.arguments;
this.reload(reloadArgs.file, reloadArgs.tmpfile, request.seq);
return {responseRequired: false};
return {response: { reloadFinished: true }, responseRequired: true};
},
[CommandNames.Saveto]: (request: protocol.Request) => {
const savetoArgs = <protocol.SavetoRequestArgs>request.arguments;
this.saveToTmp(savetoArgs.file, savetoArgs.tmpfile);
return {responseRequired: false};
return { responseRequired: false };
},
[CommandNames.Close]: (request: protocol.Request) => {
const closeArgs = <protocol.FileRequestArgs>request.arguments;
this.closeClientFile(closeArgs.file);
return {responseRequired: false};
return { responseRequired: false };
},
[CommandNames.Navto]: (request: protocol.Request) => {
const navtoArgs = <protocol.NavtoRequestArgs>request.arguments;
return {response: this.getNavigateToItems(navtoArgs.searchValue, navtoArgs.file, navtoArgs.maxResultCount), responseRequired: true};
return { response: this.getNavigateToItems(navtoArgs.searchValue, navtoArgs.file, navtoArgs.maxResultCount), responseRequired: true };
},
[CommandNames.Brace]: (request: protocol.Request) => {
const braceArguments = <protocol.FileLocationRequestArgs>request.arguments;
return {response: this.getBraceMatching(braceArguments.line, braceArguments.offset, braceArguments.file), responseRequired: true};
return { response: this.getBraceMatching(braceArguments.line, braceArguments.offset, braceArguments.file), responseRequired: true };
},
[CommandNames.NavBar]: (request: protocol.Request) => {
const navBarArgs = <protocol.FileRequestArgs>request.arguments;
return {response: this.getNavigationBarItems(navBarArgs.file), responseRequired: true};
return { response: this.getNavigationBarItems(navBarArgs.file), responseRequired: true };
},
[CommandNames.Occurrences]: (request: protocol.Request) => {
const { line, offset, file: fileName } = <protocol.FileLocationRequestArgs>request.arguments;
return {response: this.getOccurrences(line, offset, fileName), responseRequired: true};
return { response: this.getOccurrences(line, offset, fileName), responseRequired: true };
},
[CommandNames.DocumentHighlights]: (request: protocol.Request) => {
const { line, offset, file: fileName, filesToSearch } = <protocol.DocumentHighlightsRequestArgs>request.arguments;
return {response: this.getDocumentHighlights(line, offset, fileName, filesToSearch), responseRequired: true};
return { response: this.getDocumentHighlights(line, offset, fileName, filesToSearch), responseRequired: true };
},
[CommandNames.ProjectInfo]: (request: protocol.Request) => {
const { file, needFileNameList } = <protocol.ProjectInfoRequestArgs>request.arguments;
return {response: this.getProjectInfo(file, needFileNameList), responseRequired: true};
return { response: this.getProjectInfo(file, needFileNameList), responseRequired: true };
},
[CommandNames.ReloadProjects]: (request: protocol.ReloadProjectsRequest) => {
this.reloadProjects();
return {responseRequired: false};
return { responseRequired: false };
}
};
public addProtocolHandler(command: string, handler: (request: protocol.Request) => {response?: any, responseRequired: boolean}) {
public addProtocolHandler(command: string, handler: (request: protocol.Request) => { response?: any, responseRequired: boolean }) {
if (this.handlers[command]) {
throw new Error(`Protocol handler already exists for command "${command}"`);
}
this.handlers[command] = handler;
}
public executeCommand(request: protocol.Request): {response?: any, responseRequired?: boolean} {
public executeCommand(request: protocol.Request): { response?: any, responseRequired?: boolean } {
const handler = this.handlers[request.command];
if (handler) {
return handler(request);
@@ -1072,7 +1168,7 @@ namespace ts.server {
else {
this.projectService.log("Unrecognized JSON command: " + JSON.stringify(request));
this.output(undefined, CommandNames.Unknown, request.seq, "Unrecognized JSON command: " + request.command);
return {responseRequired: false};
return { responseRequired: false };
}
}

View File

@@ -1,6 +1,5 @@
{
"compilerOptions": {
"module": "commonjs",
"noImplicitAny": true,
"removeComments": true,
"preserveConstEnums": true,