Skip to content

Commit

Permalink
Decouple tsconfig.json projects from source roots
Browse files Browse the repository at this point in the history
  • Loading branch information
jeffrey-easyesi committed Oct 2, 2017
1 parent 9fa51dc commit 36859d4
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 62 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Download the latest netbeanstypescript.nbm file from the [Releases](https://gith

### Notes

* All .ts/.tsx files under one source root are currently assumed to be part of one TypeScript project.
* All .ts/.tsx files under a directory containing a tsconfig.json file are assumed to be part of that TypeScript project.
* By default, "implicit any" errors are enabled, but are shown as warnings rather than errors. You may explicitly specify `"noImplicitAny": false` in a TypeScript project's tsconfig.json to disable "implicit any" errors altogether.

### Versioning
Expand Down
108 changes: 67 additions & 41 deletions server/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,10 @@ declare var require: any;
declare module process { var stdin: any, stdout: any; }
declare class Set<T> { add(t: T): void; has(t: T): boolean; }

var version = 0;
var files: {[name: string]: {version: string; snapshot: SnapshotImpl}} = {};
var builtinLibs: {[name: string]: string} = {};
var docRegistry = ts.createDocumentRegistry(ts.sys.useCaseSensitiveFileNames);

var implicitAnyErrors: {[code: number]: boolean} = {};
[[2602, 2602], [7000, 7026], [7031, 7034]].forEach(([lo, hi]) => {
Expand All @@ -79,15 +82,12 @@ var implicitAnyErrors: {[code: number]: boolean} = {};
});

class HostImpl implements ts.LanguageServiceHost {
version = 0;
files: {[name: string]: {version: string; snapshot: SnapshotImpl}} = {};
cachedConfig: {
path: string;
parseError: ts.Diagnostic;
compileOnSave: boolean;
pcl: ts.ParsedCommandLine;
} = null;
constructor(public root: string) {}
constructor(public path: string, public isConfig: boolean) {}
log(s: string) {
process.stdout.write('L' + JSON.stringify(s) + '\n');
}
Expand All @@ -104,30 +104,30 @@ class HostImpl implements ts.LanguageServiceHost {
return ts.getNewLineCharacter(this.configUpToDate().pcl.options);
}
getProjectVersion() {
return String(this.version);
return String(version);
}
getScriptFileNames() {
return this.configUpToDate().pcl.fileNames;
}
getScriptVersion(fileName: string) {
if (fileName in builtinLibs) {
return "0";
} else if (this.files[fileName]) {
return this.files[fileName].version;
} else if (files[fileName]) {
return files[fileName].version;
}
return this.getProjectVersion();
}
getScriptSnapshot(fileName: string): ts.IScriptSnapshot {
if (fileName in builtinLibs) {
return new SnapshotImpl(builtinLibs[fileName]);
} else if (this.files[fileName]) {
return this.files[fileName].snapshot;
} else if (files[fileName]) {
return files[fileName].snapshot;
}
var text = ts.sys.readFile(fileName);
return typeof text === 'string' ? new SnapshotImpl(text) : undefined;
}
getCurrentDirectory() {
return this.root;
return "";
}
getDefaultLibFileName(options: ts.CompilerOptions): string {
return "(builtin)/" + ts.getDefaultLibFileName(options);
Expand All @@ -152,20 +152,14 @@ class HostImpl implements ts.LanguageServiceHost {
}
configUpToDate() {
if (! this.cachedConfig) {
var path = this.root + "/";
var parsed: { config?: any; error?: ts.Diagnostic } = {};
var configFiles = Object.keys(this.files)
.filter(name => ts.fileExtensionIs(name, '.json'));
if (configFiles.length) {
// We only support one project per source root for now; if there are multiple
// tsconfig.json files under this root, pick the one with the shortest path.
configFiles.sort((a, b) => a.length - b.length || a.localeCompare(b))[0];
path = configFiles[0];
parsed = ts.parseConfigFileTextToJson(path, this.files[path].snapshot.text);
if (this.isConfig) {
parsed = ts.readConfigFile(this.path, this.readFile);
} else {
parsed = { config: { files: [this.path] } }
}
var dir = ts.getDirectoryPath(path);
var dir = ts.getDirectoryPath(this.path);
this.cachedConfig = {
path: path,
parseError: parsed.error,
compileOnSave: parsed.config ? parsed.config.compileOnSave : undefined,
pcl: ts.parseJsonConfigFileContent(parsed.config || {}, ts.sys, dir)
Expand Down Expand Up @@ -199,24 +193,8 @@ class SnapshotImpl implements ts.IScriptSnapshot {
}

class Program {
host = new HostImpl(this.root);
service = ts.createLanguageService(this.host);
constructor(public root: string) {}
updateFile(fileName: string, newText: string, modified: boolean) {
this.host.version++;
if (! (fileName in this.host.files) || /\.json$/.test(fileName)) {
this.host.cachedConfig = null;
}
this.host.files[fileName] = {
version: String(this.host.version),
snapshot: new SnapshotImpl(newText)
};
}
deleteFile(fileName: string) {
this.host.version++;
this.host.cachedConfig = null;
delete this.host.files[fileName];
}
service = ts.createLanguageService(this.host, docRegistry);
constructor(public host: HostImpl) {}
fileInProject(fileName: string) {
return !!this.service.getProgram().getSourceFile(ts.normalizeSlashes(fileName));
}
Expand All @@ -225,15 +203,15 @@ class Program {
if (! this.fileInProject(fileName)) {
return {
errs: [],
metaErrors: ["File " + fileName + " is not in project defined by " + config.path]
metaErrors: ["File " + fileName + " is not in project defined by " + this.host.path]
};
}
function errText(diag: ts.Diagnostic): string {
return ts.flattenDiagnosticMessageText(diag.messageText, "\n");
}
var metaErrors = config.parseError
? [errText(config.parseError)]
: config.pcl.errors.map(diag => config.path + ": " + errText(diag));
: config.pcl.errors.map(diag => this.host.path + ": " + errText(diag));
this.service.getCompilerOptionsDiagnostics().forEach(diag => {
metaErrors.push("Project error: " + errText(diag));
});
Expand Down Expand Up @@ -667,6 +645,54 @@ class Program {
}
}

var programCache: {[path: string]: Program} = {};

function clearProgramCache() {
for (var path in programCache) {
programCache[path] && programCache[path].service.dispose();
}
programCache = {};
}

function updateFile(fileName: string, newText: string) {
version++;
if (! (fileName in files) || /\.json$/.test(fileName)) {
clearProgramCache();
}
files[fileName] = {
version: String(version),
snapshot: new SnapshotImpl(newText)
};
}

function deleteFile(fileName: string) {
version++;
clearProgramCache();
delete files[fileName];
}

function fileCall(method: keyof Program, fileName: string/*, ...*/) {
var p = programCache[fileName];
if (! p) {
// Walk up the directory tree looking for tsconfig.json
p = (function getConfiguredProgram(dir: string) {
if (! (dir in programCache)) {
var config = ts.combinePaths(dir, "tsconfig.json");
if (ts.sys.fileExists(config)) {
programCache[dir] = new Program(new HostImpl(config, true));
} else {
var parentDir = ts.getDirectoryPath(dir);
programCache[dir] = parentDir === dir ? null : getConfiguredProgram(parentDir);
}
}
return programCache[dir];
})(ts.getDirectoryPath(fileName));
// If no tsconfig.json found, create a program with only this file
programCache[fileName] = p || (p = new Program(new HostImpl(fileName, false)));
}
return (<Function>p[method]).apply(p, [].slice.call(arguments, 1));
}

require('readline').createInterface(process.stdin, process.stdout).on('line', (l: string) => {
try {
var r = JSON.stringify(eval(l));
Expand Down
34 changes: 14 additions & 20 deletions src/netbeanstypescript/TSService.java
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,6 @@ private static class NodeJSProcess {
BufferedReader stdout;
String error;
Set<Integer> supportedCodeFixes = new HashSet<>();
int nextProgId = 0;

NodeJSProcess() throws Exception {
log.info("Starting nodejs");
Expand Down Expand Up @@ -212,22 +211,17 @@ void close() throws IOException {

private static class ProgramData {
final FileObject root;
final String progVar;
final Map<String, FileData> byRelativePath = new HashMap<>();
final List<FileObject> needCompileOnSave = new ArrayList<>();
boolean needErrorsUpdate;
Object currentErrorsUpdate;

ProgramData(FileObject root) throws Exception {
this.root = root;
progVar = "p" + nodejs.nextProgId++;
StringBuilder newProgram = new StringBuilder(progVar).append("= new Program(");
stringToJS(newProgram, root.getPath());
nodejs.eval(newProgram.append(")\n").toString());
}

Object call(String method, Object... args) {
StringBuilder sb = new StringBuilder(progVar).append('.').append(method).append('(');
StringBuilder sb = new StringBuilder(method).append('(');
for (Object arg: args) {
if (sb.charAt(sb.length() - 1) != '(') sb.append(',');
if (arg instanceof CharSequence) {
Expand Down Expand Up @@ -261,8 +255,11 @@ String removeFile(Indexable indexable) throws Exception {
return null;
}

void dispose() throws Exception {
nodejs.eval("delete " + progVar + "\n");
void removeAll() {
for (FileData fd: byRelativePath.values()) {
call("deleteFile", fd.path);
}
byRelativePath.clear();
}
}

Expand Down Expand Up @@ -395,7 +392,7 @@ public void run() {
if (fi == null) {
continue;
}
JSONObject errors = (JSONObject) program.call("getDiagnostics", fi.path);
JSONObject errors = (JSONObject) program.call("fileCall", "getDiagnostics", fi.path);
if (errors != null) {
ErrorsCache.setErrors(rootURI, fi.indexable,
(List<JSONObject>) errors.get("errs"), errorConvertor);
Expand Down Expand Up @@ -432,11 +429,7 @@ static void removeProgram(URL rootURL) {
}
}

try {
program.dispose();
} catch (Exception e) {
throw new RuntimeException(e);
}
program.removeAll();

if (programs.isEmpty()) {
log.info("No programs left; shutting down nodejs");
Expand Down Expand Up @@ -481,7 +474,7 @@ static List<DefaultError> getDiagnostics(Snapshot snapshot) {
null, fo, 0, 1, true, Severity.ERROR));
}

JSONObject diags = (JSONObject) fd.program.call("getDiagnostics", fd.path);
JSONObject diags = (JSONObject) fd.program.call("fileCall", "getDiagnostics", fd.path);
if (diags == null) {
return Arrays.asList(new DefaultError(null,
nodejs.error != null ? nodejs.error : "Error in getDiagnostics",
Expand Down Expand Up @@ -519,10 +512,11 @@ public static Object call(String method, FileObject fileObj, Object... args) {
if (fd == null) {
return null;
}
Object[] filenameAndArgs = new Object[args.length + 1];
filenameAndArgs[0] = fd.path;
System.arraycopy(args, 0, filenameAndArgs, 1, args.length);
return fd.program.call(method, filenameAndArgs);
Object[] filenameAndArgs = new Object[args.length + 2];
filenameAndArgs[0] = method;
filenameAndArgs[1] = fd.path;
System.arraycopy(args, 0, filenameAndArgs, 2, args.length);
return fd.program.call("fileCall", filenameAndArgs);
} finally {
lock.unlock();
}
Expand Down

0 comments on commit 36859d4

Please sign in to comment.