Merge pull request #2450 from Microsoft/tsconfigServer

Add support to TypeScript server for tsconfig.json files.
This commit is contained in:
Steve Lucco 2015-03-24 14:38:26 -07:00
commit baac6d8a60
13 changed files with 248 additions and 109 deletions

View File

@ -10,6 +10,22 @@ module ts {
/** The version of the TypeScript compiler release */
export let version = "1.5.0.0";
export function findConfigFile(searchPath: string): string {
var fileName = "tsconfig.json";
while (true) {
if (sys.fileExists(fileName)) {
return fileName;
}
var parentPath = getDirectoryPath(searchPath);
if (parentPath === searchPath) {
break;
}
searchPath = parentPath;
fileName = "../" + fileName;
}
return undefined;
}
export function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost {
let currentDirectory: string;
let existingDirectories: Map<boolean> = {};

View File

@ -132,23 +132,6 @@ module ts {
return typeof JSON === "object" && typeof JSON.parse === "function";
}
function findConfigFile(): string {
var searchPath = normalizePath(sys.getCurrentDirectory());
var fileName = "tsconfig.json";
while (true) {
if (sys.fileExists(fileName)) {
return fileName;
}
var parentPath = getDirectoryPath(searchPath);
if (parentPath === searchPath) {
break;
}
searchPath = parentPath;
fileName = "../" + fileName;
}
return undefined;
}
export function executeCommandLine(args: string[]): void {
var commandLine = parseCommandLine(args);
var configFileName: string; // Configuration file name (if any)
@ -198,7 +181,8 @@ module ts {
}
}
else if (commandLine.fileNames.length === 0 && isJSONSupported()) {
configFileName = findConfigFile();
var searchPath = normalizePath(sys.getCurrentDirectory());
configFileName = findConfigFile(searchPath);
}
if (commandLine.fileNames.length === 0 && !configFileName) {

View File

@ -272,14 +272,24 @@ 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) {
this.compilerService = new CompilerService(this,projectOptions && projectOptions.compilerOptions);
}
addOpenRef() {
this.openRefCount++;
}
deleteOpenRef() {
this.openRefCount--;
return this.openRefCount;
}
openReferencedFile(filename: string) {
@ -299,6 +309,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 +389,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 +503,77 @@ module ts.server {
this.printProjects();
}
addOpenFile(info: ScriptInfo) {
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);
}
updateConfiguredProjectList() {
var configuredProjects: Project[] = [];
for (var i = 0, len = this.configuredProjects.length; i < len; i++) {
if (this.configuredProjects[i].openRefCount > 0) {
configuredProjects.push(this.configuredProjects[i]);
}
this.openFileRoots = openFileRoots;
this.openFileRoots.push(info);
}
this.configuredProjects = configuredProjects;
}
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) {
if (this.setConfiguredProjectRoot(info)) {
this.openFileRootsConfigured.push(info);
}
else {
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.updateConfiguredProjectList();
}
/**
* 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 +582,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 +636,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 +756,23 @@ module ts.server {
* @param filename is absolute pathname
*/
openClientFile(filename: string) {
// TODO: tsconfig check
var info = this.openFile(filename, true);
openClientFile(fileName: string) {
var searchPath = ts.normalizePath(getDirectoryPath(fileName));
var configFileName = ts.findConfigFile(searchPath);
if (configFileName) {
configFileName = getAbsolutePath(configFileName, searchPath);
}
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.log("Opened configuration file " + configFileName,"Info");
this.configuredProjects.push(configResult.project);
}
}
var info = this.openFile(fileName, true);
this.addOpenFile(info);
this.printProjects();
return info;
@ -699,19 +793,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 +805,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 +825,42 @@ 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;
}
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 +869,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,10 +898,10 @@ module ts.server {
}
}
createProject(projectFilename: string) {
var eproj = new Project(this);
eproj.projectFilename = projectFilename;
return eproj;
createProject(projectFilename: string, projectOptions?: ProjectOptions) {
var project = new Project(this, projectOptions);
project.projectFilename = projectFilename;
return project;
}
}
@ -811,14 +910,17 @@ 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();
}

View File

@ -550,11 +550,11 @@ module ts.server {
}
return completions.entries.reduce((result: protocol.CompletionEntry[], entry: ts.CompletionEntry) => {
if (completions.isMemberCompletion || entry.name.indexOf(prefix) == 0) {
if (completions.isMemberCompletion || (entry.name.toLowerCase().indexOf(prefix.toLowerCase()) == 0)) {
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
View 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"
]
}

View File

@ -1475,6 +1475,7 @@ declare module "typescript" {
declare module "typescript" {
/** The version of the TypeScript compiler release */
let version: string;
function findConfigFile(searchPath: string): string;
function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost;
function getPreEmitDiagnostics(program: Program): Diagnostic[];
function flattenDiagnosticMessageText(messageText: string | DiagnosticMessageChain, newLine: string): string;

View File

@ -4736,6 +4736,10 @@ declare module "typescript" {
let version: string;
>version : string
function findConfigFile(searchPath: string): string;
>findConfigFile : (searchPath: string) => string
>searchPath : string
function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost;
>createCompilerHost : (options: CompilerOptions, setParentNodes?: boolean) => CompilerHost
>options : CompilerOptions

View File

@ -1506,6 +1506,7 @@ declare module "typescript" {
declare module "typescript" {
/** The version of the TypeScript compiler release */
let version: string;
function findConfigFile(searchPath: string): string;
function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost;
function getPreEmitDiagnostics(program: Program): Diagnostic[];
function flattenDiagnosticMessageText(messageText: string | DiagnosticMessageChain, newLine: string): string;

View File

@ -4882,6 +4882,10 @@ declare module "typescript" {
let version: string;
>version : string
function findConfigFile(searchPath: string): string;
>findConfigFile : (searchPath: string) => string
>searchPath : string
function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost;
>createCompilerHost : (options: CompilerOptions, setParentNodes?: boolean) => CompilerHost
>options : CompilerOptions

View File

@ -1507,6 +1507,7 @@ declare module "typescript" {
declare module "typescript" {
/** The version of the TypeScript compiler release */
let version: string;
function findConfigFile(searchPath: string): string;
function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost;
function getPreEmitDiagnostics(program: Program): Diagnostic[];
function flattenDiagnosticMessageText(messageText: string | DiagnosticMessageChain, newLine: string): string;

View File

@ -4832,6 +4832,10 @@ declare module "typescript" {
let version: string;
>version : string
function findConfigFile(searchPath: string): string;
>findConfigFile : (searchPath: string) => string
>searchPath : string
function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost;
>createCompilerHost : (options: CompilerOptions, setParentNodes?: boolean) => CompilerHost
>options : CompilerOptions

View File

@ -1544,6 +1544,7 @@ declare module "typescript" {
declare module "typescript" {
/** The version of the TypeScript compiler release */
let version: string;
function findConfigFile(searchPath: string): string;
function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost;
function getPreEmitDiagnostics(program: Program): Diagnostic[];
function flattenDiagnosticMessageText(messageText: string | DiagnosticMessageChain, newLine: string): string;

View File

@ -5005,6 +5005,10 @@ declare module "typescript" {
let version: string;
>version : string
function findConfigFile(searchPath: string): string;
>findConfigFile : (searchPath: string) => string
>searchPath : string
function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost;
>createCompilerHost : (options: CompilerOptions, setParentNodes?: boolean) => CompilerHost
>options : CompilerOptions