////////////////////////////////////////////////////////////////////////////////
// Copyright 2019 Prominic.NET, Inc.
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
// http://www.apache.org/licenses/LICENSE-2.0 
// 
// Unless required by applicable law or agreed to in writing, software 
// distributed under the License is distributed on an "AS IS" BASIS, 
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and 
// limitations under the License
// 
// Author: Prominic.NET, Inc.
// No warranty of merchantability or fitness of any kind. 
// Use this software at your own risk.
////////////////////////////////////////////////////////////////////////////////
package net.prominic.groovyls;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.CompletionItemKind;
import org.eclipse.lsp4j.CompletionList;
import org.eclipse.lsp4j.CompletionParams;
import org.eclipse.lsp4j.DidOpenTextDocumentParams;
import org.eclipse.lsp4j.MessageActionItem;
import org.eclipse.lsp4j.MessageParams;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.PublishDiagnosticsParams;
import org.eclipse.lsp4j.ShowMessageRequestParams;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.TextDocumentItem;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.lsp4j.services.LanguageClient;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import net.prominic.groovyls.config.CompilationUnitFactory;

class GroovyServicesCompletionTests {
	private static final String LANGUAGE_GROOVY = "groovy";
	private static final String PATH_WORKSPACE = "./build/test_workspace/";
	private static final String PATH_SRC = "./src/main/groovy";

	private GroovyServices services;
	private Path workspaceRoot;
	private Path srcRoot;

	@BeforeEach
	void setup() {
		workspaceRoot = Paths.get(System.getProperty("user.dir")).resolve(PATH_WORKSPACE);
		srcRoot = workspaceRoot.resolve(PATH_SRC);
		if (!Files.exists(srcRoot)) {
			srcRoot.toFile().mkdirs();
		}

		services = new GroovyServices(new CompilationUnitFactory());
		services.setWorkspaceRoot(workspaceRoot);
		services.connect(new LanguageClient() {

			@Override
			public void telemetryEvent(Object object) {

			}

			@Override
			public CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams requestParams) {
				return null;
			}

			@Override
			public void showMessage(MessageParams messageParams) {

			}

			@Override
			public void publishDiagnostics(PublishDiagnosticsParams diagnostics) {

			}

			@Override
			public void logMessage(MessageParams message) {

			}
		});
	}

	@AfterEach
	void tearDown() {
		services = null;
		workspaceRoot = null;
		srcRoot = null;
	}

	@Test
	void testMemberAccessOnLocalVariableAfterDot() throws Exception {
		Path filePath = srcRoot.resolve("Completion.groovy");
		String uri = filePath.toUri().toString();
		StringBuilder contents = new StringBuilder();
		contents.append("class Completion {\n");
		contents.append("  public Completion() {\n");
		contents.append("    String localVar\n");
		contents.append("    localVar.\n");
		contents.append("  }\n");
		contents.append("}");
		TextDocumentItem textDocumentItem = new TextDocumentItem(uri, LANGUAGE_GROOVY, 1, contents.toString());
		services.didOpen(new DidOpenTextDocumentParams(textDocumentItem));
		TextDocumentIdentifier textDocument = new TextDocumentIdentifier(uri);
		Position position = new Position(3, 13);
		Either<List<CompletionItem>, CompletionList> result = services
				.completion(new CompletionParams(textDocument, position)).get();
		Assertions.assertTrue(result.isLeft());
		List<CompletionItem> items = result.getLeft();
		Assertions.assertTrue(items.size() > 0);
		List<CompletionItem> filteredItems = items.stream().filter(item -> {
			return item.getLabel().equals("charAt") && item.getKind().equals(CompletionItemKind.Method);
		}).collect(Collectors.toList());
		Assertions.assertEquals(1, filteredItems.size());
	}

	@Test
	void testMemberAccessOnMemberVariableAfterDot() throws Exception {
		Path filePath = srcRoot.resolve("Completion.groovy");
		String uri = filePath.toUri().toString();
		StringBuilder contents = new StringBuilder();
		contents.append("class Completion {\n");
		contents.append("  String memberVar\n");
		contents.append("  public Completion() {\n");
		contents.append("    memberVar.\n");
		contents.append("  }\n");
		contents.append("}");
		TextDocumentItem textDocumentItem = new TextDocumentItem(uri, LANGUAGE_GROOVY, 1, contents.toString());
		services.didOpen(new DidOpenTextDocumentParams(textDocumentItem));
		TextDocumentIdentifier textDocument = new TextDocumentIdentifier(uri);
		Position position = new Position(3, 14);
		Either<List<CompletionItem>, CompletionList> result = services
				.completion(new CompletionParams(textDocument, position)).get();
		Assertions.assertTrue(result.isLeft());
		List<CompletionItem> items = result.getLeft();
		Assertions.assertTrue(items.size() > 0);
		List<CompletionItem> filteredItems = items.stream().filter(item -> {
			return item.getLabel().equals("charAt") && item.getKind().equals(CompletionItemKind.Method);
		}).collect(Collectors.toList());
		Assertions.assertEquals(1, filteredItems.size());
	}

	@Test
	void testMemberAccessOnThisAfterDot() throws Exception {
		Path filePath = srcRoot.resolve("Completion.groovy");
		String uri = filePath.toUri().toString();
		StringBuilder contents = new StringBuilder();
		contents.append("class Completion {\n");
		contents.append("  String memberVar\n");
		contents.append("  public Completion() {\n");
		contents.append("    this.\n");
		contents.append("  }\n");
		contents.append("}");
		TextDocumentItem textDocumentItem = new TextDocumentItem(uri, LANGUAGE_GROOVY, 1, contents.toString());
		services.didOpen(new DidOpenTextDocumentParams(textDocumentItem));
		TextDocumentIdentifier textDocument = new TextDocumentIdentifier(uri);
		Position position = new Position(3, 9);
		Either<List<CompletionItem>, CompletionList> result = services
				.completion(new CompletionParams(textDocument, position)).get();
		Assertions.assertTrue(result.isLeft());
		List<CompletionItem> items = result.getLeft();
		Assertions.assertTrue(items.size() > 0);
		List<CompletionItem> filteredItems = items.stream().filter(item -> {
			return item.getLabel().equals("memberVar") && item.getKind().equals(CompletionItemKind.Field);
		}).collect(Collectors.toList());
		Assertions.assertEquals(1, filteredItems.size());
	}

	@Test
	void testMemberAccessOnClassAfterDot() throws Exception {
		Path filePath = srcRoot.resolve("Completion.groovy");
		String uri = filePath.toUri().toString();
		StringBuilder contents = new StringBuilder();
		contents.append("class Completion {\n");
		contents.append("  public Completion() {\n");
		contents.append("    Completion.\n");
		contents.append("  }\n");
		contents.append("  public static void staticMethod() {}\n");
		contents.append("}");
		TextDocumentItem textDocumentItem = new TextDocumentItem(uri, LANGUAGE_GROOVY, 1, contents.toString());
		services.didOpen(new DidOpenTextDocumentParams(textDocumentItem));
		TextDocumentIdentifier textDocument = new TextDocumentIdentifier(uri);
		Position position = new Position(2, 15);
		Either<List<CompletionItem>, CompletionList> result = services
				.completion(new CompletionParams(textDocument, position)).get();
		Assertions.assertTrue(result.isLeft());
		List<CompletionItem> items = result.getLeft();
		Assertions.assertTrue(items.size() > 0);
		List<CompletionItem> filteredItems = items.stream().filter(item -> {
			return item.getLabel().equals("staticMethod") && item.getKind().equals(CompletionItemKind.Method);
		}).collect(Collectors.toList());
		Assertions.assertEquals(1, filteredItems.size());
	}

	@Test
	void testMemberAccessOnLocalArrayAfterDot() throws Exception {
		Path filePath = srcRoot.resolve("Completion.groovy");
		String uri = filePath.toUri().toString();
		StringBuilder contents = new StringBuilder();
		contents.append("class Completion {\n");
		contents.append("  public Completion() {\n");
		contents.append("    String[] localVar\n");
		contents.append("    localVar[0].\n");
		contents.append("  }\n");
		contents.append("}");
		TextDocumentItem textDocumentItem = new TextDocumentItem(uri, LANGUAGE_GROOVY, 1, contents.toString());
		services.didOpen(new DidOpenTextDocumentParams(textDocumentItem));
		TextDocumentIdentifier textDocument = new TextDocumentIdentifier(uri);
		Position position = new Position(3, 16);
		Either<List<CompletionItem>, CompletionList> result = services
				.completion(new CompletionParams(textDocument, position)).get();
		Assertions.assertTrue(result.isLeft());
		List<CompletionItem> items = result.getLeft();
		Assertions.assertTrue(items.size() > 0);
		List<CompletionItem> filteredItems = items.stream().filter(item -> {
			return item.getLabel().equals("charAt") && item.getKind().equals(CompletionItemKind.Method);
		}).collect(Collectors.toList());
		Assertions.assertEquals(1, filteredItems.size());
	}

	@Test
	void testMemberAccessOnLocalVariableWithPartialPropertyExpression() throws Exception {
		Path filePath = srcRoot.resolve("Completion.groovy");
		String uri = filePath.toUri().toString();
		StringBuilder contents = new StringBuilder();
		contents.append("class Completion {\n");
		contents.append("  public Completion() {\n");
		contents.append("    String localVar\n");
		contents.append("    localVar.charA\n");
		contents.append("  }\n");
		contents.append("}");
		TextDocumentItem textDocumentItem = new TextDocumentItem(uri, LANGUAGE_GROOVY, 1, contents.toString());
		services.didOpen(new DidOpenTextDocumentParams(textDocumentItem));
		TextDocumentIdentifier textDocument = new TextDocumentIdentifier(uri);
		Position position = new Position(3, 18);
		Either<List<CompletionItem>, CompletionList> result = services
				.completion(new CompletionParams(textDocument, position)).get();
		Assertions.assertTrue(result.isLeft());
		List<CompletionItem> items = result.getLeft();
		List<CompletionItem> filteredItems = items.stream().filter(item -> {
			return item.getLabel().equals("charAt") && item.getKind().equals(CompletionItemKind.Method);
		}).collect(Collectors.toList());
		Assertions.assertEquals(1, filteredItems.size());
	}

	@Test
	void testMemberAccessOnThisWithMultipleResults() throws Exception {
		Path filePath = srcRoot.resolve("Completion.groovy");
		String uri = filePath.toUri().toString();
		StringBuilder contents = new StringBuilder();
		contents.append("class Completion {\n");
		contents.append("  public Completion() {\n");
		contents.append("    this.abcde\n");
		contents.append("  }\n");
		contents.append("  public abc() {}\n");
		contents.append("  public abcdef() {}\n");
		contents.append("}");
		TextDocumentItem textDocumentItem = new TextDocumentItem(uri, LANGUAGE_GROOVY, 1, contents.toString());
		services.didOpen(new DidOpenTextDocumentParams(textDocumentItem));

		TextDocumentIdentifier textDocument = new TextDocumentIdentifier(uri);

		//this first test should include both methods...
		Position position = new Position(2, 11);
		Either<List<CompletionItem>, CompletionList> result = services
				.completion(new CompletionParams(textDocument, position)).get();
		Assertions.assertTrue(result.isLeft());
		List<CompletionItem> items = result.getLeft();
		Assertions.assertEquals(2, items.size());
		List<CompletionItem> filteredItems = items.stream().filter(item -> {
			return (item.getLabel().equals("abc") && item.getKind().equals(CompletionItemKind.Method))
					|| (item.getLabel().equals("abcdef") && item.getKind().equals(CompletionItemKind.Method));
		}).collect(Collectors.toList());
		Assertions.assertEquals(2, filteredItems.size());

		//...and this one should only include the one with the longer name
		position = new Position(2, 13);
		result = services.completion(new CompletionParams(textDocument, position)).get();
		Assertions.assertTrue(result.isLeft());
		items = result.getLeft();
		Assertions.assertEquals(1, items.size());
		CompletionItem item = items.get(0);
		Assertions.assertEquals("abcdef", item.getLabel());
		Assertions.assertEquals(CompletionItemKind.Method, item.getKind());
	}

	@Test
	void testMemberAccessOnLocalVariableWithExistingVariableExpressionOnNextLine() throws Exception {
		Path filePath = srcRoot.resolve("Completion.groovy");
		String uri = filePath.toUri().toString();
		StringBuilder contents = new StringBuilder();
		contents.append("class Completion {\n");
		contents.append("  public Completion() {\n");
		contents.append("    String localVar\n");
		contents.append("    localVar.\n");
		contents.append("    localVar\n");
		contents.append("  }\n");
		contents.append("}");
		TextDocumentItem textDocumentItem = new TextDocumentItem(uri, LANGUAGE_GROOVY, 1, contents.toString());
		services.didOpen(new DidOpenTextDocumentParams(textDocumentItem));
		TextDocumentIdentifier textDocument = new TextDocumentIdentifier(uri);
		Position position = new Position(3, 13);
		Either<List<CompletionItem>, CompletionList> result = services
				.completion(new CompletionParams(textDocument, position)).get();
		Assertions.assertTrue(result.isLeft());
		List<CompletionItem> items = result.getLeft();
		Assertions.assertTrue(items.size() > 0);
		List<CompletionItem> filteredItems = items.stream().filter(item -> {
			return item.getLabel().equals("charAt") && item.getKind().equals(CompletionItemKind.Method);
		}).collect(Collectors.toList());
		Assertions.assertEquals(1, filteredItems.size());
	}

	@Test
	void testMemberAccessOnLocalVariableWithExistingMethodCallExpressionOnNextLine() throws Exception {
		Path filePath = srcRoot.resolve("Completion.groovy");
		String uri = filePath.toUri().toString();
		StringBuilder contents = new StringBuilder();
		contents.append("class Completion {\n");
		contents.append("  public Completion() {\n");
		contents.append("    String localVar\n");
		contents.append("    localVar.\n");
		contents.append("    method()\n");
		contents.append("  }\n");
		contents.append("}");
		TextDocumentItem textDocumentItem = new TextDocumentItem(uri, LANGUAGE_GROOVY, 1, contents.toString());
		services.didOpen(new DidOpenTextDocumentParams(textDocumentItem));
		TextDocumentIdentifier textDocument = new TextDocumentIdentifier(uri);
		Position position = new Position(3, 13);
		Either<List<CompletionItem>, CompletionList> result = services
				.completion(new CompletionParams(textDocument, position)).get();
		Assertions.assertTrue(result.isLeft());
		List<CompletionItem> items = result.getLeft();
		Assertions.assertTrue(items.size() > 0);
		List<CompletionItem> filteredItems = items.stream().filter(item -> {
			return item.getLabel().equals("charAt") && item.getKind().equals(CompletionItemKind.Method);
		}).collect(Collectors.toList());
		Assertions.assertEquals(1, filteredItems.size());
	}

	@Test
	void testCompletionForMemberVariableOnPartialVariableExpression() throws Exception {
		Path filePath = srcRoot.resolve("Completion.groovy");
		String uri = filePath.toUri().toString();
		StringBuilder contents = new StringBuilder();
		contents.append("class Completion {\n");
		contents.append("  String memberVar\n");
		contents.append("  public Completion() {\n");
		contents.append("    mem\n");
		contents.append("  }\n");
		contents.append("}");
		TextDocumentItem textDocumentItem = new TextDocumentItem(uri, LANGUAGE_GROOVY, 1, contents.toString());
		services.didOpen(new DidOpenTextDocumentParams(textDocumentItem));
		TextDocumentIdentifier textDocument = new TextDocumentIdentifier(uri);
		Position position = new Position(3, 7);
		Either<List<CompletionItem>, CompletionList> result = services
				.completion(new CompletionParams(textDocument, position)).get();
		Assertions.assertTrue(result.isLeft());
		List<CompletionItem> items = result.getLeft();
		List<CompletionItem> filteredItems = items.stream().filter(item -> {
			return item.getLabel().equals("memberVar") && item.getKind().equals(CompletionItemKind.Field);
		}).collect(Collectors.toList());
		Assertions.assertEquals(1, filteredItems.size());
	}

	@Test
	void testCompletionForMemberVariableOnCompleteVariableExpression() throws Exception {
		Path filePath = srcRoot.resolve("Completion.groovy");
		String uri = filePath.toUri().toString();
		StringBuilder contents = new StringBuilder();
		contents.append("class Completion {\n");
		contents.append("  String memberVar\n");
		contents.append("  public Completion() {\n");
		contents.append("    memberVar\n");
		contents.append("  }\n");
		contents.append("}");
		TextDocumentItem textDocumentItem = new TextDocumentItem(uri, LANGUAGE_GROOVY, 1, contents.toString());
		services.didOpen(new DidOpenTextDocumentParams(textDocumentItem));
		TextDocumentIdentifier textDocument = new TextDocumentIdentifier(uri);
		Position position = new Position(3, 7);
		Either<List<CompletionItem>, CompletionList> result = services
				.completion(new CompletionParams(textDocument, position)).get();
		Assertions.assertTrue(result.isLeft());
		List<CompletionItem> items = result.getLeft();
		List<CompletionItem> filteredItems = items.stream().filter(item -> {
			return item.getLabel().equals("memberVar") && item.getKind().equals(CompletionItemKind.Field);
		}).collect(Collectors.toList());
		Assertions.assertEquals(1, filteredItems.size());
	}

	@Test
	void testCompletionForMemberMethodOnPartialVariableExpression() throws Exception {
		Path filePath = srcRoot.resolve("Completion.groovy");
		String uri = filePath.toUri().toString();
		StringBuilder contents = new StringBuilder();
		contents.append("class Completion {\n");
		contents.append("  String memberMethod() {}\n");
		contents.append("  public Completion() {\n");
		contents.append("    mem\n");
		contents.append("  }\n");
		contents.append("}");
		TextDocumentItem textDocumentItem = new TextDocumentItem(uri, LANGUAGE_GROOVY, 1, contents.toString());
		services.didOpen(new DidOpenTextDocumentParams(textDocumentItem));
		TextDocumentIdentifier textDocument = new TextDocumentIdentifier(uri);
		Position position = new Position(3, 7);
		Either<List<CompletionItem>, CompletionList> result = services
				.completion(new CompletionParams(textDocument, position)).get();
		Assertions.assertTrue(result.isLeft());
		List<CompletionItem> items = result.getLeft();
		List<CompletionItem> filteredItems = items.stream().filter(item -> {
			return item.getLabel().equals("memberMethod") && item.getKind().equals(CompletionItemKind.Method);
		}).collect(Collectors.toList());
		Assertions.assertEquals(1, filteredItems.size());
	}

	@Test
	void testCompletionForMemberMethodOnCompleteVariableExpression() throws Exception {
		Path filePath = srcRoot.resolve("Completion.groovy");
		String uri = filePath.toUri().toString();
		StringBuilder contents = new StringBuilder();
		contents.append("class Completion {\n");
		contents.append("  String memberMethod() {}\n");
		contents.append("  public Completion() {\n");
		contents.append("    memberMethod\n");
		contents.append("  }\n");
		contents.append("}");
		TextDocumentItem textDocumentItem = new TextDocumentItem(uri, LANGUAGE_GROOVY, 1, contents.toString());
		services.didOpen(new DidOpenTextDocumentParams(textDocumentItem));
		TextDocumentIdentifier textDocument = new TextDocumentIdentifier(uri);
		Position position = new Position(3, 7);
		Either<List<CompletionItem>, CompletionList> result = services
				.completion(new CompletionParams(textDocument, position)).get();
		Assertions.assertTrue(result.isLeft());
		List<CompletionItem> items = result.getLeft();
		List<CompletionItem> filteredItems = items.stream().filter(item -> {
			return item.getLabel().equals("memberMethod") && item.getKind().equals(CompletionItemKind.Method);
		}).collect(Collectors.toList());
		Assertions.assertEquals(1, filteredItems.size());
	}

	@Test
	void testCompletionForParameterOnPartialVariableExpression() throws Exception {
		Path filePath = srcRoot.resolve("Completion.groovy");
		String uri = filePath.toUri().toString();
		StringBuilder contents = new StringBuilder();
		contents.append("class Completion {\n");
		contents.append("  public void testMethod(String paramName) {\n");
		contents.append("    par\n");
		contents.append("  }\n");
		contents.append("}");
		TextDocumentItem textDocumentItem = new TextDocumentItem(uri, LANGUAGE_GROOVY, 1, contents.toString());
		services.didOpen(new DidOpenTextDocumentParams(textDocumentItem));
		TextDocumentIdentifier textDocument = new TextDocumentIdentifier(uri);
		Position position = new Position(2, 7);
		Either<List<CompletionItem>, CompletionList> result = services
				.completion(new CompletionParams(textDocument, position)).get();
		Assertions.assertTrue(result.isLeft());
		List<CompletionItem> items = result.getLeft();
		List<CompletionItem> filteredItems = items.stream().filter(item -> {
			return item.getLabel().equals("paramName") && item.getKind().equals(CompletionItemKind.Variable);
		}).collect(Collectors.toList());
		Assertions.assertEquals(1, filteredItems.size());
	}

	@Test
	void testCompletionForParameterOnCompleteVariableExpression() throws Exception {
		Path filePath = srcRoot.resolve("Completion.groovy");
		String uri = filePath.toUri().toString();
		StringBuilder contents = new StringBuilder();
		contents.append("class Completion {\n");
		contents.append("  public void testMethod(String paramName) {\n");
		contents.append("    paramName\n");
		contents.append("  }\n");
		contents.append("}");
		TextDocumentItem textDocumentItem = new TextDocumentItem(uri, LANGUAGE_GROOVY, 1, contents.toString());
		services.didOpen(new DidOpenTextDocumentParams(textDocumentItem));
		TextDocumentIdentifier textDocument = new TextDocumentIdentifier(uri);
		Position position = new Position(2, 13);
		Either<List<CompletionItem>, CompletionList> result = services
				.completion(new CompletionParams(textDocument, position)).get();
		Assertions.assertTrue(result.isLeft());
		List<CompletionItem> items = result.getLeft();
		List<CompletionItem> filteredItems = items.stream().filter(item -> {
			return item.getLabel().equals("paramName") && item.getKind().equals(CompletionItemKind.Variable);
		}).collect(Collectors.toList());
		Assertions.assertEquals(1, filteredItems.size());
	}

	@Test
	void testCompletionForLocalVariableOnPartialVariableExpression() throws Exception {
		Path filePath = srcRoot.resolve("Completion.groovy");
		String uri = filePath.toUri().toString();
		StringBuilder contents = new StringBuilder();
		contents.append("class Completion {\n");
		contents.append("  public void testMethod(String paramName) {\n");
		contents.append("    String localVar\n");
		contents.append("    loc\n");
		contents.append("  }\n");
		contents.append("}");
		TextDocumentItem textDocumentItem = new TextDocumentItem(uri, LANGUAGE_GROOVY, 1, contents.toString());
		services.didOpen(new DidOpenTextDocumentParams(textDocumentItem));
		TextDocumentIdentifier textDocument = new TextDocumentIdentifier(uri);
		Position position = new Position(3, 7);
		Either<List<CompletionItem>, CompletionList> result = services
				.completion(new CompletionParams(textDocument, position)).get();
		Assertions.assertTrue(result.isLeft());
		List<CompletionItem> items = result.getLeft();
		List<CompletionItem> filteredItems = items.stream().filter(item -> {
			return item.getLabel().equals("localVar") && item.getKind().equals(CompletionItemKind.Variable);
		}).collect(Collectors.toList());
		Assertions.assertEquals(1, filteredItems.size());
	}

	@Test
	void testCompletionForLocalVariableOnCompleteVariableExpression() throws Exception {
		Path filePath = srcRoot.resolve("Completion.groovy");
		String uri = filePath.toUri().toString();
		StringBuilder contents = new StringBuilder();
		contents.append("class Completion {\n");
		contents.append("  public void testMethod() {\n");
		contents.append("    String localVar\n");
		contents.append("    localVar\n");
		contents.append("  }\n");
		contents.append("}");
		TextDocumentItem textDocumentItem = new TextDocumentItem(uri, LANGUAGE_GROOVY, 1, contents.toString());
		services.didOpen(new DidOpenTextDocumentParams(textDocumentItem));
		TextDocumentIdentifier textDocument = new TextDocumentIdentifier(uri);
		Position position = new Position(3, 12);
		Either<List<CompletionItem>, CompletionList> result = services
				.completion(new CompletionParams(textDocument, position)).get();
		Assertions.assertTrue(result.isLeft());
		List<CompletionItem> items = result.getLeft();
		List<CompletionItem> filteredItems = items.stream().filter(item -> {
			return item.getLabel().equals("localVar") && item.getKind().equals(CompletionItemKind.Variable);
		}).collect(Collectors.toList());
		Assertions.assertEquals(1, filteredItems.size());
	}

	@Test
	void testCompletionForLocalVariableOnPartialVariableExpressionInsideBlock() throws Exception {
		Path filePath = srcRoot.resolve("Completion.groovy");
		String uri = filePath.toUri().toString();
		StringBuilder contents = new StringBuilder();
		contents.append("class Completion {\n");
		contents.append("  public void testMethod(String paramName) {\n");
		contents.append("    String localVar\n");
		contents.append("    if(true) {\n");
		contents.append("      loc\n");
		contents.append("    }\n");
		contents.append("  }\n");
		contents.append("}");
		TextDocumentItem textDocumentItem = new TextDocumentItem(uri, LANGUAGE_GROOVY, 1, contents.toString());
		services.didOpen(new DidOpenTextDocumentParams(textDocumentItem));
		TextDocumentIdentifier textDocument = new TextDocumentIdentifier(uri);
		Position position = new Position(4, 9);
		Either<List<CompletionItem>, CompletionList> result = services
				.completion(new CompletionParams(textDocument, position)).get();
		Assertions.assertTrue(result.isLeft());
		List<CompletionItem> items = result.getLeft();
		List<CompletionItem> filteredItems = items.stream().filter(item -> {
			return item.getLabel().equals("localVar") && item.getKind().equals(CompletionItemKind.Variable);
		}).collect(Collectors.toList());
		Assertions.assertEquals(1, filteredItems.size());
	}
}