// Copyright 2012 Google Inc. All Rights Reserved.
//
// 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.
package com.google.collide.client.code.autocomplete.codegraph;
import com.google.collide.client.code.autocomplete.AutocompleteProposal;
import com.google.collide.client.code.autocomplete.Autocompleter;
import com.google.collide.client.code.autocomplete.TestUtils;
import com.google.collide.client.code.autocomplete.codegraph.CodeGraphAutocompleterTest.MockProposalBuilder;
import com.google.collide.client.testutil.SynchronousTestCase;
import com.google.collide.client.util.PathUtil;
import com.google.collide.codemirror2.State;
import com.google.collide.codemirror2.SyntaxType;
import com.google.collide.dto.CodeBlock;
import com.google.collide.dto.client.DtoClientImpls.CodeBlockImpl;
import com.google.collide.dto.client.DtoClientImpls.CodeGraphImpl;
import com.google.collide.dto.client.DtoClientImpls.MockCodeBlockImpl;
import com.google.collide.json.client.JsoArray;
import com.google.collide.json.client.JsoStringMap;
import com.google.collide.json.client.JsoStringSet;
import com.google.collide.json.shared.JsonArray;
import com.google.collide.json.shared.JsonStringSet;
import com.google.collide.shared.document.Document;
import com.google.collide.shared.document.Position;
import com.google.collide.shared.util.JsonCollections;
/**
* Tests for ProposalBuilder.
*
*/
public class ScopeTrieBuilderTest extends SynchronousTestCase {
static final int LAST_COLUMN = 99;
static final String OBJECT_1 = "MyObject";
static final String METHOD_1 = "method1";
static final String METHOD_2 = "method2";
private PathUtil path;
@Override
public void gwtSetUp() throws Exception {
super.gwtSetUp();
path = new PathUtil("/foo.js");
}
@Override
public String getModuleName() {
return "com.google.collide.client.TestCode";
}
private CodeBlockImpl createCodeBlockTree() {
// We create code blocks for the following pseudo-code:
// MyObject {
// function method1() {
// }
// function method2() {
// var var1 {
// foo: ''
// }
// }
// }
CodeBlockImpl contextFile = MockCodeBlockImpl
.make()
.setId("0")
.setName(path.getPathString())
.setBlockType(CodeBlock.Type.VALUE_FILE)
.setStartLineNumber(0)
.setStartColumn(0)
.setEndLineNumber(0)
.setEndColumn(LAST_COLUMN)
.setChildren(JsoArray.<CodeBlock>create());
CodeBlockImpl objectBlock = MockCodeBlockImpl
.make()
.setId("1")
.setBlockType(CodeBlock.Type.VALUE_FIELD)
.setName(OBJECT_1)
.setStartLineNumber(0)
.setStartColumn(0)
.setEndLineNumber(0)
.setEndColumn(LAST_COLUMN)
.setChildren(JsoArray.<CodeBlock>create());
contextFile.getChildren().add(objectBlock);
CodeBlockImpl method1 = MockCodeBlockImpl
.make()
.setId("2")
.setBlockType(CodeBlock.Type.VALUE_FUNCTION)
.setName(METHOD_1)
.setStartLineNumber(0)
.setStartColumn(10)
.setEndLineNumber(0)
.setEndColumn(49)
.setChildren(JsoArray.<CodeBlock>create());
CodeBlockImpl method2 = MockCodeBlockImpl
.make()
.setId("3")
.setBlockType(CodeBlock.Type.VALUE_FUNCTION)
.setName(METHOD_2)
.setStartLineNumber(0)
.setStartColumn(50)
.setEndLineNumber(0)
.setEndColumn(LAST_COLUMN)
.setChildren(JsoArray.<CodeBlock>create());
objectBlock.getChildren().add(method1);
objectBlock.getChildren().add(method2);
CodeBlockImpl localVar = MockCodeBlockImpl
.make()
.setId("4")
.setBlockType(CodeBlock.Type.VALUE_FIELD)
.setName("var1")
.setStartLineNumber(0)
.setStartColumn(60)
.setEndLineNumber(0)
.setEndColumn(79)
.setChildren(JsoArray.<CodeBlock>create());
method2.getChildren().add(localVar);
CodeBlockImpl localVarField = MockCodeBlockImpl
.make()
.setId("5")
.setBlockType(CodeBlock.Type.VALUE_FIELD)
.setName("foo")
.setStartLineNumber(0)
.setStartColumn(65)
.setEndLineNumber(0)
.setEndColumn(74)
.setChildren(JsoArray.<CodeBlock>create());
localVar.getChildren().add(localVarField);
return contextFile;
}
/**
* Check that produced proposals are equal to the given ones.
* @param builder proposal producer
* @param expected expected proposal, concatenated with "," separator
* @param triggeringString context
* @param isThisContext flag indicating "this."-like previous context
* @return produced proposals (for deeper analysis)
*/
private JsonArray<AutocompleteProposal> checkProposals(ScopeTrieBuilder builder,
String[] expected, String triggeringString, boolean isThisContext) {
Position cursor = new Position(
Document.createEmpty().getFirstLineInfo(), triggeringString.length());
ProposalBuilder<State> proposalBuilder = new MockProposalBuilder();
CompletionContext<State> context = new CompletionContext<State>(
"", triggeringString, isThisContext, CompletionType.GLOBAL, null, 0);
JsonArray<AutocompleteProposal> proposals = proposalBuilder.doGetProposals(
context, cursor, builder);
assertEquals(JsonCollections.createStringSet(expected), TestUtils.createNameSet(proposals));
return proposals;
}
private ScopeTrieBuilder createScopeTrieBuilder(CodeBlock codeBlock) {
CodeFile codeFile = new CodeFile(path);
codeFile.setRootCodeBlock(codeBlock);
return new ScopeTrieBuilder(codeFile, SyntaxType.JS);
}
public void testEmptyDataSources() {
CodeFile codeFile = new CodeFile(path);
checkProposals(new ScopeTrieBuilder(codeFile, SyntaxType.JS), new String[0], "foo", false);
}
public void testExternalFileTopLevelProposals() {
CodeBlockImpl file1 = MockCodeBlockImpl.make().setBlockType(CodeBlock.Type.VALUE_FILE)
.setChildren(JsoArray.<CodeBlock>create())
.setId("0")
.setName("/file1.js");
file1.getChildren().add(MockCodeBlockImpl
.make()
.setId("1")
.setBlockType(CodeBlock.Type.VALUE_FUNCTION)
.setName("foobar")
.setStartLineNumber(0)
.setStartColumn(0)
.setEndLineNumber(0)
.setEndColumn(49));
file1.getChildren().add(MockCodeBlockImpl
.make()
.setId("2")
.setBlockType(CodeBlock.Type.VALUE_FUNCTION)
.setName("barbaz")
.setStartLineNumber(0)
.setStartColumn(0)
.setEndLineNumber(0)
.setEndColumn(49));
CodeBlockImpl file2 = MockCodeBlockImpl.make().setBlockType(CodeBlock.Type.VALUE_FILE)
.setChildren(JsoArray.<CodeBlock>create())
.setId("1")
.setName("/file2.js");
file2.getChildren().add(MockCodeBlockImpl
.make()
.setId("1")
.setBlockType(CodeBlock.Type.VALUE_FUNCTION)
.setName("foobaz")
.setStartLineNumber(0)
.setStartColumn(0)
.setEndLineNumber(0)
.setEndColumn(49));
file2.getChildren().add(MockCodeBlockImpl
.make()
.setId("2")
.setBlockType(CodeBlock.Type.VALUE_FUNCTION)
.setName("barfoo")
.setStartLineNumber(0)
.setStartColumn(0)
.setEndLineNumber(0)
.setEndColumn(49));
CodeFile codeFile = new CodeFile(path);
ScopeTrieBuilder builder = new ScopeTrieBuilder(codeFile, SyntaxType.JS);
JsoStringMap<CodeBlock> codeBlockMap = JsoStringMap.create();
codeBlockMap.put(file1.getName(), file1);
codeBlockMap.put(file2.getName(), file2);
builder.setCodeGraph(CodeGraphImpl.make().setCodeBlockMap(codeBlockMap));
JsonArray<AutocompleteProposal> proposals = checkProposals(
builder, new String[] {"foobar", "foobaz"}, "foo", false);
assertEquals(new PathUtil("/file1.js"),
TestUtils.findProposalByName(proposals, "foobar").getPath());
assertEquals(new PathUtil("/file2.js"),
TestUtils.findProposalByName(proposals, "foobaz").getPath());
checkProposals(builder, new String[] {"barbaz", "barfoo", "foobar", "foobaz"}, "", false);
// Check this proposals do not receive top-level items.
checkProposals(builder, new String[0], "", true);
}
public void testLexicalScopeCompletionIncludesAncestorScopesAndChildScopes() {
ScopeTrieBuilder builder = setupBuilder();
Position cursor = new Position(Document.createEmpty().getFirstLineInfo(), 82);
ProposalBuilder<State> proposalBuilder = new MockProposalBuilder();
CompletionContext<State> context = new CompletionContext<State>(
"", "", false, CompletionType.GLOBAL, null, 0);
JsonArray<AutocompleteProposal> proposals = proposalBuilder.doGetProposals(
context, cursor, builder);
JsonStringSet expected = JsonCollections.createStringSet(OBJECT_1, METHOD_2, METHOD_1, "var1");
assertEquals(expected, TestUtils.createNameSet(proposals));
}
public void testCaseInsensitiveSearch() {
assertTrue(Autocompleter.CASE_INSENSITIVE);
ScopeTrieBuilder builder = setupBuilder();
Position cursor = new Position(Document.createEmpty().getFirstLineInfo(), 82);
ProposalBuilder<State> proposalBuilder = new MockProposalBuilder();
CompletionContext<State> context = new CompletionContext<State>(
"", "MEtH", false, CompletionType.GLOBAL, null, 0);
JsonArray<AutocompleteProposal> proposals = proposalBuilder.doGetProposals(
context, cursor, builder);
JsonStringSet expected = JsonCollections.createStringSet("method2", "method1");
assertEquals(expected, TestUtils.createNameSet(proposals));
}
public void testThisCompletionIncludesDirectParentScope() {
ScopeTrieBuilder builder = setupBuilder();
Position cursor = new Position(Document.createEmpty().getFirstLineInfo(), 86);
ProposalBuilder<State> proposalBuilder = new MockProposalBuilder();
CompletionContext<State> context = new CompletionContext<State>(
"", "", true, CompletionType.PROPERTY, null, 0);
JsonArray<AutocompleteProposal> proposals = proposalBuilder.doGetProposals(
context, cursor, builder);
assertEquals(JsonCollections.createStringSet(METHOD_2, METHOD_1),
TestUtils.createNameSet(proposals));
}
public void testScopeEndBoundry() {
ScopeTrieBuilder builder = setupBuilder();
ProposalBuilder<State> proposalBuilder = new MockProposalBuilder();
CompletionContext<State> context = new CompletionContext<State>(
"", "", true, CompletionType.PROPERTY, null, 0);
Position cursor = new Position(Document.createEmpty().getFirstLineInfo(), LAST_COLUMN + 1);
assertFalse(proposalBuilder.doGetProposals(context, cursor, builder).isEmpty());
cursor = new Position(Document.createEmpty().getFirstLineInfo(), LAST_COLUMN + 2);
assertTrue(proposalBuilder.doGetProposals(context, cursor, builder).isEmpty());
}
/**
* Checks that even if we have problems with resolving scope, we try to
* search with raw "previousContext".
*/
public void testBareScopePrefixMinimum() {
ScopeTrieBuilder scopeTrieBuilder = new ScopeTrieBuilder(new CodeFile(path), SyntaxType.JS);
String previousContext = "moo.";
CompletionContext<State> context = new CompletionContext<State>(
previousContext, "", false, CompletionType.PROPERTY, null, 0);
Position cursor = new Position(Document.createEmpty().getFirstLineInfo(), 1);
JsoStringSet prefixes = scopeTrieBuilder.calculateScopePrefixes(context, cursor);
assertNotNull(prefixes);
assertFalse(prefixes.isEmpty());
assertTrue(prefixes.contains(previousContext));
}
/**
* Checks that for scoped position "full scope path + previous context" is
* included to prefix list.
*/
public void testFullLexicalScopePrefix() {
ScopeTrieBuilder builder = setupBuilder();
String previousContext = "moo.";
CompletionContext<State> context = new CompletionContext<State>(
previousContext, "", false, CompletionType.PROPERTY, null, 0);
Position cursor = new Position(Document.createEmpty().getFirstLineInfo(), 20);
JsoStringSet prefixes = builder.calculateScopePrefixes(context, cursor);
assertNotNull(prefixes);
assertFalse(prefixes.isEmpty());
assertTrue(prefixes.contains(OBJECT_1 + "." + METHOD_1 + "." + previousContext));
}
public void testScopeResolutionForExpandingContext() {
ScopeTrieBuilder builder = setupBuilder();
ProposalBuilder<State> proposalBuilder = new MockProposalBuilder();
CompletionContext<State> context = new CompletionContext<State>(
"", "", true, CompletionType.PROPERTY, null, 0);
Position cursor = new Position(Document.createEmpty().getFirstLineInfo(), LAST_COLUMN + 2);
assertTrue(proposalBuilder.doGetProposals(context, cursor, builder).isEmpty());
context = new CompletionContext<State>(
OBJECT_1 + ".", "", true, CompletionType.PROPERTY, null, 0);
cursor = new Position(Document.createEmpty().getFirstLineInfo(), LAST_COLUMN + 2);
assertFalse(proposalBuilder.doGetProposals(context, cursor, builder).isEmpty());
}
private ScopeTrieBuilder setupBuilder() {
CodeBlock contextFile = createCodeBlockTree();
JsoStringMap<CodeBlock> codeBlockMap = JsoStringMap.create();
codeBlockMap.put(contextFile.getName(), contextFile);
ScopeTrieBuilder builder = createScopeTrieBuilder(contextFile);
builder.setCodeGraph(CodeGraphImpl.make().setCodeBlockMap(codeBlockMap));
return builder;
}
}