mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-11 10:00:13 -06:00
Added support in the server for tsconfig.json. When a host opens a
file F, the server will check whether F is configured by a tsconfig.json file. If so, the project specified by the tsconfig.json file becomes the LS context for F.
This commit is contained in:
parent
0d2a5bbf7c
commit
8798235c68
@ -272,14 +272,29 @@ module ts.server {
|
||||
|
||||
export class Project {
|
||||
compilerService: CompilerService;
|
||||
projectOptions: ProjectOptions;
|
||||
projectFilename: string;
|
||||
program: ts.Program;
|
||||
filenameToSourceFile: ts.Map<ts.SourceFile> = {};
|
||||
updateGraphSeq = 0;
|
||||
/** Used for configured projects which may have multiple open roots */
|
||||
openRefCount = 0;
|
||||
|
||||
constructor(public projectService: ProjectService) {
|
||||
this.compilerService = new CompilerService(this);
|
||||
constructor(public projectService: ProjectService, public projectOptions?: ProjectOptions) {
|
||||
if (projectOptions && projectOptions.compilerOptions) {
|
||||
this.compilerService = new CompilerService(this,projectOptions.compilerOptions);
|
||||
}
|
||||
else {
|
||||
this.compilerService = new CompilerService(this);
|
||||
}
|
||||
}
|
||||
|
||||
addOpenRef() {
|
||||
this.openRefCount++;
|
||||
}
|
||||
|
||||
deleteOpenRef() {
|
||||
this.openRefCount--;
|
||||
return this.openRefCount;
|
||||
}
|
||||
|
||||
openReferencedFile(filename: string) {
|
||||
@ -299,6 +314,10 @@ module ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
isRoot(info: ScriptInfo) {
|
||||
return this.compilerService.host.roots.some(root => root === info);
|
||||
}
|
||||
|
||||
removeReferencedFile(info: ScriptInfo) {
|
||||
this.compilerService.host.removeReferencedFile(info);
|
||||
this.updateGraph();
|
||||
@ -375,11 +394,16 @@ module ts.server {
|
||||
|
||||
export class ProjectService {
|
||||
filenameToScriptInfo: ts.Map<ScriptInfo> = {};
|
||||
// open, non-configured files in two lists
|
||||
// open, non-configured root files
|
||||
openFileRoots: ScriptInfo[] = [];
|
||||
openFilesReferenced: ScriptInfo[] = [];
|
||||
// projects covering open files
|
||||
// projects built from openFileRoots
|
||||
inferredProjects: Project[] = [];
|
||||
// projects specified by a tsconfig.json file
|
||||
configuredProjects: Project[] = [];
|
||||
// open files referenced by a project
|
||||
openFilesReferenced: ScriptInfo[] = [];
|
||||
// open files that are roots of a configured project
|
||||
openFileRootsConfigured: ScriptInfo[] = [];
|
||||
hostConfiguration: HostConfiguration;
|
||||
|
||||
constructor(public host: ServerHost, public psLogger: Logger, public eventHandler?: ProjectServiceEventHandler) {
|
||||
@ -484,44 +508,66 @@ module ts.server {
|
||||
this.printProjects();
|
||||
}
|
||||
|
||||
setConfiguredProjectRoot(info: ScriptInfo) {
|
||||
for (var i = 0, len = this.configuredProjects.length; i < len; i++) {
|
||||
let configuredProject = this.configuredProjects[i];
|
||||
if (configuredProject.isRoot(info)) {
|
||||
info.defaultProject = configuredProject;
|
||||
configuredProject.addOpenRef();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
addOpenFile(info: ScriptInfo) {
|
||||
this.findReferencingProjects(info);
|
||||
if (info.defaultProject) {
|
||||
this.openFilesReferenced.push(info);
|
||||
if (this.setConfiguredProjectRoot(info)) {
|
||||
this.openFileRootsConfigured.push(info);
|
||||
}
|
||||
else {
|
||||
// create new inferred project p with the newly opened file as root
|
||||
info.defaultProject = this.createInferredProject(info);
|
||||
var openFileRoots: ScriptInfo[] = [];
|
||||
// for each inferred project root r
|
||||
for (var i = 0, len = this.openFileRoots.length; i < len; i++) {
|
||||
var r = this.openFileRoots[i];
|
||||
// if r referenced by the new project
|
||||
if (info.defaultProject.getSourceFile(r)) {
|
||||
// remove project rooted at r
|
||||
this.inferredProjects =
|
||||
copyListRemovingItem(r.defaultProject, this.inferredProjects);
|
||||
// put r in referenced open file list
|
||||
this.openFilesReferenced.push(r);
|
||||
// set default project of r to the new project
|
||||
r.defaultProject = info.defaultProject;
|
||||
}
|
||||
else {
|
||||
// otherwise, keep r as root of inferred project
|
||||
openFileRoots.push(r);
|
||||
}
|
||||
this.findReferencingProjects(info);
|
||||
if (info.defaultProject) {
|
||||
this.openFilesReferenced.push(info);
|
||||
}
|
||||
else {
|
||||
// create new inferred project p with the newly opened file as root
|
||||
info.defaultProject = this.createInferredProject(info);
|
||||
var openFileRoots: ScriptInfo[] = [];
|
||||
// for each inferred project root r
|
||||
for (var i = 0, len = this.openFileRoots.length; i < len; i++) {
|
||||
var r = this.openFileRoots[i];
|
||||
// if r referenced by the new project
|
||||
if (info.defaultProject.getSourceFile(r)) {
|
||||
// remove project rooted at r
|
||||
this.inferredProjects =
|
||||
copyListRemovingItem(r.defaultProject, this.inferredProjects);
|
||||
// put r in referenced open file list
|
||||
this.openFilesReferenced.push(r);
|
||||
// set default project of r to the new project
|
||||
r.defaultProject = info.defaultProject;
|
||||
}
|
||||
else {
|
||||
// otherwise, keep r as root of inferred project
|
||||
openFileRoots.push(r);
|
||||
}
|
||||
}
|
||||
this.openFileRoots = openFileRoots;
|
||||
this.openFileRoots.push(info);
|
||||
}
|
||||
this.openFileRoots = openFileRoots;
|
||||
this.openFileRoots.push(info);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove this file from the set of open, non-configured files.
|
||||
* @param info The file that has been closed or newly configured
|
||||
* @param openedByConfig True if info has become a root of a configured project
|
||||
*/
|
||||
closeOpenFile(info: ScriptInfo) {
|
||||
var openFileRoots: ScriptInfo[] = [];
|
||||
var removedProject: Project;
|
||||
for (var i = 0, len = this.openFileRoots.length; i < len; i++) {
|
||||
// if closed file is root of project
|
||||
if (info == this.openFileRoots[i]) {
|
||||
if (info === this.openFileRoots[i]) {
|
||||
// remove that project and remember it
|
||||
removedProject = info.defaultProject;
|
||||
}
|
||||
@ -530,9 +576,29 @@ module ts.server {
|
||||
}
|
||||
}
|
||||
this.openFileRoots = openFileRoots;
|
||||
if (!removedProject) {
|
||||
var openFileRootsConfigured: ScriptInfo[] = [];
|
||||
|
||||
for (var i = 0, len = this.openFileRootsConfigured.length; i < len; i++) {
|
||||
if (info === this.openFileRootsConfigured[i]) {
|
||||
if (info.defaultProject.deleteOpenRef() === 0) {
|
||||
removedProject = info.defaultProject;
|
||||
}
|
||||
}
|
||||
else {
|
||||
openFileRootsConfigured.push(this.openFileRootsConfigured[i]);
|
||||
}
|
||||
}
|
||||
|
||||
this.openFileRootsConfigured = openFileRootsConfigured;
|
||||
}
|
||||
if (removedProject) {
|
||||
// remove project from inferred projects list
|
||||
this.inferredProjects = copyListRemovingItem(removedProject, this.inferredProjects);
|
||||
if (removedProject.isConfiguredProject()) {
|
||||
this.configuredProjects = copyListRemovingItem(removedProject, this.configuredProjects);
|
||||
}
|
||||
else {
|
||||
this.inferredProjects = copyListRemovingItem(removedProject, this.inferredProjects);
|
||||
}
|
||||
var openFilesReferenced: ScriptInfo[] = [];
|
||||
var orphanFiles: ScriptInfo[] = [];
|
||||
// for all open, referenced files f
|
||||
@ -564,14 +630,22 @@ module ts.server {
|
||||
var referencingProjects: Project[] = [];
|
||||
info.defaultProject = undefined;
|
||||
for (var i = 0, len = this.inferredProjects.length; i < len; i++) {
|
||||
this.inferredProjects[i].updateGraph();
|
||||
if (this.inferredProjects[i] != excludedProject) {
|
||||
if (this.inferredProjects[i].getSourceFile(info)) {
|
||||
info.defaultProject = this.inferredProjects[i];
|
||||
referencingProjects.push(this.inferredProjects[i]);
|
||||
var inferredProject = this.inferredProjects[i];
|
||||
inferredProject.updateGraph();
|
||||
if (inferredProject != excludedProject) {
|
||||
if (inferredProject.getSourceFile(info)) {
|
||||
info.defaultProject = inferredProject;
|
||||
referencingProjects.push(inferredProject);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (var i = 0, len = this.configuredProjects.length; i < len; i++) {
|
||||
var configuredProject = this.configuredProjects[i];
|
||||
configuredProject.updateGraph();
|
||||
if (configuredProject.getSourceFile(info)) {
|
||||
info.defaultProject = configuredProject;
|
||||
}
|
||||
}
|
||||
return referencingProjects;
|
||||
}
|
||||
|
||||
@ -676,9 +750,18 @@ module ts.server {
|
||||
* @param filename is absolute pathname
|
||||
*/
|
||||
|
||||
openClientFile(filename: string) {
|
||||
// TODO: tsconfig check
|
||||
var info = this.openFile(filename, true);
|
||||
openClientFile(fileName: string) {
|
||||
var configFileName = this.findConfigFile(fileName);
|
||||
if (configFileName && (!this.configProjectIsActive(configFileName))) {
|
||||
var configResult = this.openConfigFile(configFileName, fileName);
|
||||
if (!configResult.success) {
|
||||
this.log("Error opening config file " + configFileName + " " + configResult.errorMsg);
|
||||
}
|
||||
else {
|
||||
this.configuredProjects.push(configResult.project);
|
||||
}
|
||||
}
|
||||
var info = this.openFile(fileName, true);
|
||||
this.addOpenFile(info);
|
||||
this.printProjects();
|
||||
return info;
|
||||
@ -699,19 +782,6 @@ module ts.server {
|
||||
this.printProjects();
|
||||
}
|
||||
|
||||
getProjectsReferencingFile(filename: string) {
|
||||
var scriptInfo = ts.lookUp(this.filenameToScriptInfo, filename);
|
||||
if (scriptInfo) {
|
||||
var projects: Project[] = [];
|
||||
for (var i = 0, len = this.inferredProjects.length; i < len; i++) {
|
||||
if (this.inferredProjects[i].getSourceFile(scriptInfo)) {
|
||||
projects.push(this.inferredProjects[i]);
|
||||
}
|
||||
}
|
||||
return projects;
|
||||
}
|
||||
}
|
||||
|
||||
getProjectForFile(filename: string) {
|
||||
var scriptInfo = ts.lookUp(this.filenameToScriptInfo, filename);
|
||||
if (scriptInfo) {
|
||||
@ -724,9 +794,9 @@ module ts.server {
|
||||
if (scriptInfo) {
|
||||
this.psLogger.startGroup();
|
||||
this.psLogger.info("Projects for " + filename)
|
||||
var projects = this.getProjectsReferencingFile(filename);
|
||||
var projects = this.findReferencingProjects(scriptInfo);
|
||||
for (var i = 0, len = projects.length; i < len; i++) {
|
||||
this.psLogger.info("Inferred Project " + i.toString());
|
||||
this.psLogger.info("Project " + i.toString());
|
||||
}
|
||||
this.psLogger.endGroup();
|
||||
}
|
||||
@ -744,18 +814,58 @@ module ts.server {
|
||||
this.psLogger.info(project.filesToString());
|
||||
this.psLogger.info("-----------------------------------------------");
|
||||
}
|
||||
this.psLogger.info("Open file roots: ")
|
||||
for (var i = 0, len = this.configuredProjects.length; i < len; i++) {
|
||||
var project = this.configuredProjects[i];
|
||||
project.updateGraph();
|
||||
this.psLogger.info("Project (configured) " + (i+this.inferredProjects.length).toString());
|
||||
this.psLogger.info(project.filesToString());
|
||||
this.psLogger.info("-----------------------------------------------");
|
||||
}
|
||||
this.psLogger.info("Open file roots of inferred projects: ")
|
||||
for (var i = 0, len = this.openFileRoots.length; i < len; i++) {
|
||||
this.psLogger.info(this.openFileRoots[i].fileName);
|
||||
}
|
||||
this.psLogger.info("Open files referenced: ")
|
||||
this.psLogger.info("Open files referenced by inferred or configured projects: ")
|
||||
for (var i = 0, len = this.openFilesReferenced.length; i < len; i++) {
|
||||
this.psLogger.info(this.openFilesReferenced[i].fileName);
|
||||
var fileInfo = this.openFilesReferenced[i].fileName;
|
||||
if (this.openFilesReferenced[i].defaultProject.isConfiguredProject()) {
|
||||
fileInfo += " (configured)";
|
||||
}
|
||||
this.psLogger.info(fileInfo);
|
||||
}
|
||||
this.psLogger.info("Open file roots of configured projects: ")
|
||||
for (var i = 0, len = this.openFileRootsConfigured.length; i < len; i++) {
|
||||
this.psLogger.info(this.openFileRootsConfigured[i].fileName);
|
||||
}
|
||||
this.psLogger.endGroup();
|
||||
}
|
||||
|
||||
openConfigFile(configFilename: string): ProjectOpenResult {
|
||||
configProjectIsActive(fileName: string) {
|
||||
for (var i = 0, len = this.configuredProjects.length; i < len; i++) {
|
||||
if (this.configuredProjects[i].projectFilename == fileName) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
findConfigFile(openedFileName: string): string {
|
||||
var searchPath = getDirectoryPath(openedFileName);
|
||||
while (true) {
|
||||
var fileName = searchPath + ts.directorySeparator + "tsconfig.json";
|
||||
if (sys.fileExists(fileName)) {
|
||||
return fileName;
|
||||
}
|
||||
var parentPath = getDirectoryPath(searchPath);
|
||||
if (parentPath === searchPath) {
|
||||
break;
|
||||
}
|
||||
searchPath = parentPath;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
openConfigFile(configFilename: string, clientFileName?: string): ProjectOpenResult {
|
||||
configFilename = ts.normalizePath(configFilename);
|
||||
// file references will be relative to dirPath (or absolute)
|
||||
var dirPath = ts.getDirectoryPath(configFilename);
|
||||
@ -764,33 +874,27 @@ module ts.server {
|
||||
return { errorMsg: "tsconfig syntax error" };
|
||||
}
|
||||
else {
|
||||
// REVIEW: specify no base path so can get absolute path below
|
||||
var parsedCommandLine = ts.parseConfigFile(rawConfig);
|
||||
|
||||
if (parsedCommandLine.errors) {
|
||||
// TODO: gather diagnostics and transmit
|
||||
var parsedCommandLine = ts.parseConfigFile(rawConfig, dirPath);
|
||||
if (parsedCommandLine.errors && (parsedCommandLine.errors.length > 0)) {
|
||||
return { errorMsg: "tsconfig option errors" };
|
||||
}
|
||||
else if (parsedCommandLine.fileNames) {
|
||||
var proj = this.createProject(configFilename);
|
||||
var projectOptions: ProjectOptions = {
|
||||
files: parsedCommandLine.fileNames,
|
||||
compilerOptions: parsedCommandLine.options
|
||||
};
|
||||
var proj = this.createProject(configFilename, projectOptions);
|
||||
for (var i = 0, len = parsedCommandLine.fileNames.length; i < len; i++) {
|
||||
var rootFilename = parsedCommandLine.fileNames[i];
|
||||
var normRootFilename = ts.normalizePath(rootFilename);
|
||||
normRootFilename = getAbsolutePath(normRootFilename, dirPath);
|
||||
if (this.host.fileExists(normRootFilename)) {
|
||||
// TODO: pass true for file exiplicitly opened
|
||||
var info = this.openFile(normRootFilename, false);
|
||||
if (ts.sys.fileExists(rootFilename)) {
|
||||
var info = this.openFile(rootFilename, clientFileName == rootFilename);
|
||||
proj.addRoot(info);
|
||||
}
|
||||
else {
|
||||
return { errorMsg: "specified file " + rootFilename + " not found" };
|
||||
}
|
||||
}
|
||||
var projectOptions: ProjectOptions = {
|
||||
files: parsedCommandLine.fileNames,
|
||||
compilerOptions: parsedCommandLine.options
|
||||
};
|
||||
proj.setProjectOptions(projectOptions);
|
||||
proj.finishGraph();
|
||||
return { success: true, project: proj };
|
||||
}
|
||||
else {
|
||||
@ -799,8 +903,8 @@ module ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
createProject(projectFilename: string) {
|
||||
var eproj = new Project(this);
|
||||
createProject(projectFilename: string, projectOptions?: ProjectOptions) {
|
||||
var eproj = new Project(this, projectOptions);
|
||||
eproj.projectFilename = projectFilename;
|
||||
return eproj;
|
||||
}
|
||||
@ -811,20 +915,25 @@ module ts.server {
|
||||
host: LSHost;
|
||||
languageService: ts.LanguageService;
|
||||
classifier: ts.Classifier;
|
||||
settings = ts.getDefaultCompilerOptions();
|
||||
settings: ts.CompilerOptions;
|
||||
documentRegistry = ts.createDocumentRegistry();
|
||||
|
||||
constructor(public project: Project) {
|
||||
constructor(public project: Project, opt?: ts.CompilerOptions) {
|
||||
this.host = new LSHost(project.projectService.host, project);
|
||||
// override default ES6 (remove when compiler default back at ES5)
|
||||
this.settings.target = ts.ScriptTarget.ES5;
|
||||
this.host.setCompilationSettings(this.settings);
|
||||
if (opt) {
|
||||
this.setCompilerOptions(opt);
|
||||
}
|
||||
else {
|
||||
this.setCompilerOptions(ts.getDefaultCompilerOptions());
|
||||
}
|
||||
this.languageService = ts.createLanguageService(this.host, this.documentRegistry);
|
||||
this.classifier = ts.createClassifier();
|
||||
}
|
||||
|
||||
setCompilerOptions(opt: ts.CompilerOptions) {
|
||||
this.settings = opt;
|
||||
// override default ES6 (remove when compiler default back at ES5)
|
||||
this.settings.target = ts.ScriptTarget.ES5;
|
||||
this.host.setCompilationSettings(opt);
|
||||
}
|
||||
|
||||
|
||||
@ -554,7 +554,7 @@ module ts.server {
|
||||
result.push(entry);
|
||||
}
|
||||
return result;
|
||||
}, []);
|
||||
}, []).sort((a, b) => a.name.localeCompare(b.name));
|
||||
}
|
||||
|
||||
getCompletionEntryDetails(line: number, offset: number,
|
||||
|
||||
17
src/server/tsconfig.json
Normal file
17
src/server/tsconfig.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"noImplicitAny": true,
|
||||
"removeComments": true,
|
||||
"preserveConstEnums": true,
|
||||
"out": "../../built/local/tsserver.js",
|
||||
"sourceMap": true
|
||||
},
|
||||
"files": [
|
||||
"node.d.ts",
|
||||
"editorServices.ts",
|
||||
"protocol.d.ts",
|
||||
"server.ts",
|
||||
"session.ts"
|
||||
]
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user