Browse Source

[server] keep track of removed files for syntax-based completion

see #8451
Simon Krajewski 6 years ago
parent
commit
34037a6257

+ 4 - 1
src/context/compilationServer.ml

@@ -21,6 +21,7 @@ type cache = {
 	c_files : ((string * string), cached_file) Hashtbl.t;
 	c_modules : (path * string, module_def) Hashtbl.t;
 	c_directories : (string, cached_directory list) Hashtbl.t;
+	c_removed_files : (string * string,unit) Hashtbl.t;
 }
 
 type context_sign = {
@@ -46,6 +47,7 @@ let create_cache () = {
 	c_files = Hashtbl.create 0;
 	c_modules = Hashtbl.create 0;
 	c_directories = Hashtbl.create 0;
+	c_removed_files = Hashtbl.create 0;
 }
 
 let create () =
@@ -145,7 +147,8 @@ let cache_file cs key time data =
 	Hashtbl.replace cs.cache.c_files key { c_time = time; c_package = fst data; c_decls = snd data; c_module_name = None }
 
 let remove_file cs key =
-	Hashtbl.remove cs.cache.c_files key
+	Hashtbl.remove cs.cache.c_files key;
+	Hashtbl.replace cs.cache.c_removed_files key ()
 
 let remove_files cs file =
 	List.iter (fun (sign,_) -> remove_file cs (file,sign)) cs.signs

+ 3 - 1
src/context/display/displayJson.ml

@@ -204,7 +204,9 @@ let handler =
 		"server/invalidate", (fun hctx ->
 			let file = hctx.jsonrpc#get_string_param "file" in
 			let file = Path.unique_full_path file in
-			CompilationServer.taint_modules hctx.display#get_cs file;
+			let cs = hctx.display#get_cs in
+			CompilationServer.taint_modules cs file;
+			CompilationServer.remove_files cs file;
 			hctx.send_result jnull
 		);
 		"server/configure", (fun hctx ->

+ 22 - 4
src/context/display/displayToplevel.ml

@@ -84,6 +84,27 @@ let read_class_paths com timer =
 			()
 	)
 
+let init_or_update_server cs com timer_name =
+	if not (CompilationServer.is_initialized cs) then begin
+		CompilationServer.set_initialized cs;
+		read_class_paths com timer_name
+	end else begin
+		(* Iterate all removed files of the current context. If they aren't part of the context again,
+		   re-parse them and remove them from c_removed_files. *)
+		let sign = Define.get_signature com.defines in
+		let removed_removed_files = DynArray.create () in
+		Hashtbl.iter (fun (file,sign') () ->
+			if sign = sign' then begin
+				DynArray.add removed_removed_files (file,sign');
+				try
+					ignore(find_file cs (file,sign));
+				with Not_found ->
+					ignore(TypeloadParse.parse_module_file com file null_pos);
+			end;
+		) cs.cache.c_removed_files;
+		DynArray.iter (Hashtbl.remove cs.cache.c_removed_files) removed_removed_files;
+	end
+
 module CollectionContext = struct
 	open ImportStatus
 
@@ -367,10 +388,7 @@ let collect ctx tk with_type =
 		)
 	| Some cs ->
 		(* online: iter context files *)
-		if not (CompilationServer.is_initialized cs) then begin
-			CompilationServer.set_initialized cs;
-			read_class_paths ctx.com ["display";"toplevel"];
-		end;
+		init_or_update_server cs ctx.com ["display";"toplevel"];
 		let files = CompilationServer.get_file_list cs ctx.com in
 		(* Sort files by reverse distance of their package to our current package. *)
 		let files = List.map (fun (file,cfile) ->

+ 1 - 4
src/context/display/findReferences.ml

@@ -148,10 +148,7 @@ let find_possible_references kind name (pack,decls) =
 
 let find_possible_references tctx cs =
 	let name,pos,kind = Display.ReferencePosition.get () in
-	if not (CompilationServer.is_initialized cs) then begin
-		CompilationServer.set_initialized cs;
-		DisplayToplevel.read_class_paths tctx.com ["display";"references"];
-	end;
+	DisplayToplevel.init_or_update_server cs tctx.com ["display";"references"];
 	let files = CompilationServer.get_file_list cs tctx.com in
 	let t = Timer.timer ["display";"references";"candidates"] in
 	List.iter (fun (file,cfile) ->

+ 10 - 0
tests/server/src/HaxeServerTestCase.hx

@@ -167,4 +167,14 @@ class HaxeServerTestCase implements ITest {
 		}
 		Assert.fail("No such completion", p);
 	}
+
+	function assertHasNoCompletion<T>(completion:Array<DisplayItem<T>>, f:DisplayItem<T>->Bool, ?p:haxe.PosInfos) {
+		for (type in completion) {
+			if (f(type)) {
+				Assert.fail("Unexpected completion", p);
+				return;
+			}
+		}
+		Assert.pass();
+	}
 }

+ 12 - 1
tests/server/src/Main.hx

@@ -137,8 +137,10 @@ class ServerTests extends HaxeServerTestCase {
 	function testSyntaxCache() {
 		vfs.putContent("HelloWorld.hx", getTemplate("HelloWorld.hx"));
 		runHaxeJson(["-cp", "."], ServerMethods.ReadClassPaths, null);
-		vfs.putContent("Empty.hx", getTemplate("Empty.hx"));
+		vfs.putContent("Empty.hx", "");
 		runHaxeJson([], ServerMethods.ModuleCreated, {file: new FsPath("Empty.hx")});
+		vfs.putContent("Empty.hx", getTemplate("Empty.hx"));
+		runHaxeJson([], ServerMethods.Invalidate, {file: new FsPath("Empty.hx")});
 		runHaxeJson([], DisplayMethods.Completion, {file: new FsPath("HelloWorld.hx"), offset: 75, wasAutoTriggered: false});
 		var completion = parseCompletion();
 		assertHasCompletion(completion, module -> switch (module.kind) {
@@ -149,6 +151,15 @@ class ServerTests extends HaxeServerTestCase {
 			case Type: module.args.path.typeName == "Empty";
 			case _: false;
 		});
+		// check removal
+		vfs.putContent("Empty.hx", "");
+		runHaxeJson([], ServerMethods.Invalidate, {file: new FsPath("Empty.hx")});
+		runHaxeJson([], DisplayMethods.Completion, {file: new FsPath("HelloWorld.hx"), offset: 75, wasAutoTriggered: false});
+		var completion = parseCompletion();
+		assertHasNoCompletion(completion, module -> switch (module.kind) {
+			case Type: module.args.path.typeName == "Empty";
+			case _: false;
+		});
 	}
 
 	function testVectorInliner() {