JSDoc type parser - improve handling of union types
diff --git a/plugins/org.eclipse.dltk.javascript.core/src/org/eclipse/dltk/javascript/typeinfo/JSDocTypeParser.java b/plugins/org.eclipse.dltk.javascript.core/src/org/eclipse/dltk/javascript/typeinfo/JSDocTypeParser.java
index 2b35aeb..768650e 100644
--- a/plugins/org.eclipse.dltk.javascript.core/src/org/eclipse/dltk/javascript/typeinfo/JSDocTypeParser.java
+++ b/plugins/org.eclipse.dltk.javascript.core/src/org/eclipse/dltk/javascript/typeinfo/JSDocTypeParser.java
@@ -65,7 +65,7 @@
 
 	public JSType parse(String input) throws ParseException {
 		final ANTLRStringStream stream = new ANTLRStringStream(input);
-		final JSType type = parse(stream);
+		final JSType type = parse(stream, true);
 		if (stream.LT(1) != CharStream.EOF) {
 			throw new ParseException("Unexpected "
 					+ stream.substring(stream.index(), stream.size() - 1),
@@ -74,7 +74,19 @@
 		return type;
 	}
 
-	protected JSType parse(CharStream input) throws ParseException {
+	/**
+	 * Parses the next type expression. {@link UnionType}s are handled at this
+	 * level, parsing of parts is delegated to {@link #parseType(CharStream)}.
+	 * 
+	 * @param input
+	 * @param autoUnion
+	 *            if <code>true</code> then it is allowed to parse all parts of
+	 *            the union type, but if <code>false</code> then unit type
+	 *            declaration should be enclosed in parenthesis -
+	 *            <code>'('</code> and <code>')'</code>.
+	 */
+	protected JSType parse(CharStream input, boolean autoUnion)
+			throws ParseException {
 		skipSpaces(input);
 		final List<JSType> types = new ArrayList<JSType>();
 		final boolean inParenthese = input.LT(1) == '(';
@@ -87,7 +99,7 @@
 			if (type != null) {
 				types.add(type);
 				skipSpaces(input);
-				if (input.LT(1) == '|') {
+				if ((inParenthese || autoUnion) && input.LT(1) == '|') {
 					input.consume();
 					skipSpaces(input);
 					continue;
@@ -154,7 +166,7 @@
 				final int baseEnd = input.index();
 				final String baseType = input.substring(start, baseEnd - 1);
 				input.consume();
-				final List<JSType> typeParams = parseParams(input);
+				final List<JSType> typeParams = parseTypeParams(input);
 				match(input, '>');
 				JSType type = createGenericType(baseType, typeParams);
 				if (extension != null) {
@@ -166,7 +178,7 @@
 				final String baseType = input.substring(start, baseEnd - 1);
 				input.consume();
 				input.consume();
-				final List<JSType> typeParams = parseParams(input);
+				final List<JSType> typeParams = parseTypeParams(input);
 				match(input, '>');
 				JSType type = createGenericType(baseType, typeParams);
 				if (extension != null) {
@@ -185,7 +197,7 @@
 				if (input.LT(1) == ':') {
 					input.consume();
 					skipSpaces(input);
-					functionType.setReturnType(parseType(input));
+					functionType.setReturnType(parse(input, false));
 				}
 				return checkIfArray(input, functionType);
 			} else if (ch == '[') {
@@ -326,10 +338,11 @@
 		return TypeUtil.ref(baseType);
 	}
 
-	protected List<JSType> parseParams(CharStream input) throws ParseException {
+	protected List<JSType> parseTypeParams(CharStream input)
+			throws ParseException {
 		final List<JSType> types = new ArrayList<JSType>();
 		for (;;) {
-			final JSType type = parse(input);
+			final JSType type = parse(input, true);
 			if (type != null) {
 				types.add(type);
 				skipSpaces(input);
@@ -360,7 +373,7 @@
 					squareBracket = true;
 				}
 			}
-			final JSType type = parse(input);
+			final JSType type = parse(input, true);
 			if (type != null) {
 				final Parameter parameter = TypeInfoModelFactory.eINSTANCE
 						.createParameter();
@@ -435,7 +448,7 @@
 
 				if (input.LT(1) == ':') {
 					input.consume();
-					final JSType memberType = parse(input);
+					final JSType memberType = parse(input, true);
 					ch = input.LT(1);
 					if (ch == '=') {
 						input.consume();
diff --git a/tests/org.eclipse.dltk.javascript.core.tests/src/org/eclipse/dltk/javascript/core/tests/typeinfo/JSDocTypeParserTests.java b/tests/org.eclipse.dltk.javascript.core.tests/src/org/eclipse/dltk/javascript/core/tests/typeinfo/JSDocTypeParserTests.java
index 3d8e30f..d555413 100644
--- a/tests/org.eclipse.dltk.javascript.core.tests/src/org/eclipse/dltk/javascript/core/tests/typeinfo/JSDocTypeParserTests.java
+++ b/tests/org.eclipse.dltk.javascript.core.tests/src/org/eclipse/dltk/javascript/core/tests/typeinfo/JSDocTypeParserTests.java
@@ -28,6 +28,7 @@
 import org.eclipse.dltk.javascript.typeinfo.model.RecordType;
 import org.eclipse.dltk.javascript.typeinfo.model.SimpleType;
 import org.eclipse.dltk.javascript.typeinfo.model.UnionType;
+import org.eclipse.emf.common.util.EList;
 
 @SuppressWarnings("restriction")
 public class JSDocTypeParserTests extends TestCase {
@@ -196,4 +197,34 @@
 		assertRef("Number", type.getReturnType());
 	}
 
+	public void testUnitonWithFunction1() {
+		final UnionType type = (UnionType) parse("String|function(Number):Number|String");
+		final EList<JSType> parts = type.getTargets();
+		assertEquals(3, parts.size());
+
+		final SimpleType part0 = (SimpleType) parts.get(0);
+		assertEquals("String", part0.getName());
+
+		final FunctionType part1 = (FunctionType) parts.get(1);
+		assertEquals("Number", part1.getReturnType().getName());
+
+		final SimpleType part2 = (SimpleType) parts.get(2);
+		assertEquals("String", part2.getName());
+	}
+
+	public void testUnitonWithFunction2() {
+		final UnionType type = (UnionType) parse("String|function(Number):(Number|String)");
+		final EList<JSType> parts = type.getTargets();
+		assertEquals(2, parts.size());
+
+		final SimpleType part0 = (SimpleType) parts.get(0);
+		assertEquals("String", part0.getName());
+
+		final FunctionType part1 = (FunctionType) parts.get(1);
+		final UnionType resultType = (UnionType) part1.getReturnType();
+		assertEquals(2, resultType.getTargets().size());
+		assertEquals("Number", resultType.getTargets().get(0).getName());
+		assertEquals("String", resultType.getTargets().get(1).getName());
+	}
+
 }