Skip to content

Commit

Permalink
Allow TypeScript syntax in arrow function parameters and return types
Browse files Browse the repository at this point in the history
Merges pull request #1378 from Dominator008/closure-compiler
Closes #1378
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=121864559
  • Loading branch information
Dominator008 authored and blickly committed May 9, 2016
1 parent f5569ec commit 7a8706c
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 20 deletions.
66 changes: 52 additions & 14 deletions src/com/google/javascript/jscomp/parsing/parser/Parser.java
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -123,7 +123,9 @@
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Set;


/** /**
* Parses a javascript file. * Parses a javascript file.
Expand Down Expand Up @@ -172,6 +174,7 @@ public class Parser {
private final CommentRecorder commentRecorder = new CommentRecorder(); private final CommentRecorder commentRecorder = new CommentRecorder();
private final ArrayDeque<Boolean> inGeneratorContext = new ArrayDeque<>(); private final ArrayDeque<Boolean> inGeneratorContext = new ArrayDeque<>();
private FeatureSet features = FeatureSet.ES3; private FeatureSet features = FeatureSet.ES3;
private final Set<Token> colonTypeTokens = new LinkedHashSet<>();


public Parser( public Parser(
Config config, ErrorReporter errorReporter, Config config, ErrorReporter errorReporter,
Expand Down Expand Up @@ -1040,15 +1043,11 @@ private ParseTree parseArrowFunctionTail(
SourcePosition start, SourcePosition start,
GenericTypeListTree generics, GenericTypeListTree generics,
FormalParameterListTree formalParameterList, FormalParameterListTree formalParameterList,
ParseTree returnType,
Expression expressionIn) { Expression expressionIn) {


inGeneratorContext.addLast(false); inGeneratorContext.addLast(false);


ParseTree returnType = null;
if (peek(TokenType.COLON)) {
returnType = parseTypeAnnotation();
}

if (peekImplicitSemiColon()) { if (peekImplicitSemiColon()) {
reportError("No newline allowed before '=>'"); reportError("No newline allowed before '=>'");
} }
Expand All @@ -1057,7 +1056,7 @@ private ParseTree parseArrowFunctionTail(
if (peek(TokenType.OPEN_CURLY)) { if (peek(TokenType.OPEN_CURLY)) {
functionBody = parseFunctionBody(); functionBody = parseFunctionBody();
} else { } else {
functionBody = parseAssignment(expressionIn); functionBody = parseAssignment(expressionIn, false);
} }


FunctionDeclarationTree declaration = new FunctionDeclarationTree( FunctionDeclarationTree declaration = new FunctionDeclarationTree(
Expand Down Expand Up @@ -1892,7 +1891,7 @@ private ImmutableList<ParseTree> parseCaseClauses() {
switch (peekType()) { switch (peekType()) {
case CASE: case CASE:
eat(TokenType.CASE); eat(TokenType.CASE);
ParseTree expression = parseExpression(); ParseTree expression = parse(Expression.NORMAL, false);
eat(TokenType.COLON); eat(TokenType.COLON);
ImmutableList<ParseTree> statements = parseCaseStatementsOpt(); ImmutableList<ParseTree> statements = parseCaseStatementsOpt();
result.add(new CaseClauseTree(getTreeLocation(start), expression, statements)); result.add(new CaseClauseTree(getTreeLocation(start), expression, statements));
Expand Down Expand Up @@ -2595,8 +2594,12 @@ private boolean peekExpression() {
} }


private ParseTree parse(Expression expressionIn) { private ParseTree parse(Expression expressionIn) {
return parse(expressionIn, true);
}

private ParseTree parse(Expression expressionIn, boolean mayHaveColonType) {
SourcePosition start = getTreeStartLocation(); SourcePosition start = getTreeStartLocation();
ParseTree result = parseAssignment(expressionIn); ParseTree result = parseAssignment(expressionIn, mayHaveColonType);
if (peek(TokenType.COMMA) && !peek(1, TokenType.SPREAD)) { if (peek(TokenType.COMMA) && !peek(1, TokenType.SPREAD)) {
ImmutableList.Builder<ParseTree> exprs = ImmutableList.builder(); ImmutableList.Builder<ParseTree> exprs = ImmutableList.builder();
exprs.add(result); exprs.add(result);
Expand All @@ -2619,17 +2622,44 @@ private boolean peekAssignmentExpression() {
} }


private ParseTree parseAssignment(Expression expressionIn) { private ParseTree parseAssignment(Expression expressionIn) {
return parseAssignment(expressionIn, true);
}

private void reportInvalidColonTypes() {
for (Token colon : colonTypeTokens) {
reportError(colon, "invalid location for colon type expression");
}
colonTypeTokens.clear();
}

private ParseTree parseAssignment(Expression expressionIn, boolean mayHaveColonType) {
if (peek(TokenType.YIELD) && inGeneratorContext()) { if (peek(TokenType.YIELD) && inGeneratorContext()) {
return parseYield(expressionIn); return parseYield(expressionIn);
} }


SourcePosition start = getTreeStartLocation(); SourcePosition start = getTreeStartLocation();
// TODO(blickly): Allow TypeScript syntax in arrow function parameters GenericTypeListTree generics = maybeParseGenericTypes();
ParseTree left = parseConditional(expressionIn); ParseTree left = parseConditional(expressionIn);

ParseTree returnType = null;
if (peek(TokenType.COLON) && mayHaveColonType) {
colonTypeTokens.add(peekToken());
returnType = parseTypeAnnotation();
}

if (peek(TokenType.ARROW)) { if (peek(TokenType.ARROW)) {
FormalParameterListTree params = transformArrowFunctionParameters(start, left); FormalParameterListTree params = transformArrowFunctionParameters(start, left);
return parseArrowFunctionTail(start, null, params, expressionIn); colonTypeTokens.clear();
if (peek(TokenType.COLON)) {
parseTypeAnnotation();
}
return parseArrowFunctionTail(start, generics, params, returnType, expressionIn);
} }

if (generics != null) {
reportError("invalid location for generics");
}

if (left.type == ParseTreeType.FORMAL_PARAMETER_LIST) { if (left.type == ParseTreeType.FORMAL_PARAMETER_LIST) {
reportError("invalid paren expression"); reportError("invalid paren expression");
} }
Expand All @@ -2640,9 +2670,17 @@ private ParseTree parseAssignment(Expression expressionIn) {
reportError("invalid assignment target"); reportError("invalid assignment target");
} }
Token operator = nextToken(); Token operator = nextToken();
ParseTree right = parseAssignment(expressionIn); ParseTree right = parseAssignment(expressionIn, mayHaveColonType);
return new BinaryOperatorTree(getTreeLocation(start), left, operator, right); return new BinaryOperatorTree(getTreeLocation(start), left, operator, right);
} }

if (peek(TokenType.CLOSE_PAREN)) {
if (!peek(1, TokenType.ARROW) && !peek(1, TokenType.COLON)) {
reportInvalidColonTypes();
}
} else if (!peek(TokenType.COMMA)) {
reportInvalidColonTypes();
}
return left; return left;
} }


Expand All @@ -2655,7 +2693,7 @@ private FormalParameterListTree transformArrowFunctionParameters(
case PAREN_EXPRESSION: case PAREN_EXPRESSION:
resetScanner(tree); resetScanner(tree);
// If we fail to parse as an ArrowFunction paramater list then // If we fail to parse as an ArrowFunction paramater list then
// parseFormalParameterList will take care reporting errors. // parseFormalParameterList will take care of reporting errors.
return parseFormalParameterList(ParamContext.IMPLEMENTATION); return parseFormalParameterList(ParamContext.IMPLEMENTATION);
case FORMAL_PARAMETER_LIST: case FORMAL_PARAMETER_LIST:
return tree.asFormalParameterList(); return tree.asFormalParameterList();
Expand Down Expand Up @@ -2739,9 +2777,9 @@ private ParseTree parseConditional(Expression expressionIn) {
ParseTree condition = parseLogicalOR(expressionIn); ParseTree condition = parseLogicalOR(expressionIn);
if (peek(TokenType.QUESTION)) { if (peek(TokenType.QUESTION)) {
eat(TokenType.QUESTION); eat(TokenType.QUESTION);
ParseTree left = parseAssignment(expressionIn); ParseTree left = parseAssignment(expressionIn, false);
eat(TokenType.COLON); eat(TokenType.COLON);
ParseTree right = parseAssignment(expressionIn); ParseTree right = parseAssignment(expressionIn, false);
return new ConditionalExpressionTree( return new ConditionalExpressionTree(
getTreeLocation(start), condition, left, right); getTreeLocation(start), condition, left, right);
} }
Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ public void testGenericClass() {


public void testGenericFunction() { public void testGenericFunction() {
test("function foo<T>() {}", "/** @template T */ function foo() {}"); test("function foo<T>() {}", "/** @template T */ function foo() {}");
// test("var x = <K, V>(p) => 3;", "var x = /** @template K, V */ (p) => 3"); test("var x = <K, V>(p) => 3;", "var x = /** @template K, V */ (p) => 3");
test("class Foo { f<T>() {} }", "class Foo { /** @template T */ f() {} }"); test("class Foo { f<T>() {} }", "class Foo { /** @template T */ f() {} }");
test("(function<T>() {})();", "(/** @template T */ function() {})();"); test("(function<T>() {})();", "(/** @template T */ function() {})();");
test("function* foo<T>() {}", "/** @template T */ function* foo() {}"); test("function* foo<T>() {}", "/** @template T */ function* foo() {}");
Expand Down
15 changes: 15 additions & 0 deletions test/com/google/javascript/jscomp/parsing/ParserTest.java
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -227,6 +227,21 @@ public void testNonDuplicateLabelCrossFunction() {
parse("foo:(function(){foo:2})"); parse("foo:(function(){foo:2})");
} }


public void testConditional() {
parse("1 ? 2 ? 3 : 4 : 5;");
parse("1 ? (x,y,z) : n;");

mode = LanguageMode.ECMASCRIPT6;
expectedFeatures = FeatureSet.ES6_IMPL;
Node hook = parse(LINE_JOINER.join(
"function f(cond) {",
" return cond ?",
" (v = 7, f = () => v) :",
" n => 2 * n;",
"}")).getFirstChild().getLastChild().getFirstFirstChild();
assertNode(hook).hasType(Token.HOOK);
}

public void testLinenoCharnoAssign1() throws Exception { public void testLinenoCharnoAssign1() throws Exception {
Node assign = parse("a = b").getFirstFirstChild(); Node assign = parse("a = b").getFirstFirstChild();


Expand Down
37 changes: 32 additions & 5 deletions test/com/google/javascript/jscomp/parsing/TypeSyntaxTest.java
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ public void testFunctionParamDeclaration_destructuringObject() {
parse("function foo({x}: any) {\n}"); parse("function foo({x}: any) {\n}");
} }


public void disabled_testFunctionParamDeclaration_arrow() { public void testFunctionParamDeclaration_arrow() {
Node fn = parse("(x: string) => 'hello' + x;").getFirstFirstChild(); Node fn = parse("(x: string) => 'hello' + x;").getFirstFirstChild();
Node param = fn.getSecondChild().getFirstChild(); Node param = fn.getSecondChild().getFirstChild();
assertDeclaredType("string type", stringType(), param); assertDeclaredType("string type", stringType(), param);
Expand All @@ -172,7 +172,7 @@ public void testFunctionReturn() {
assertDeclaredType("string type", stringType(), fn); assertDeclaredType("string type", stringType(), fn);
} }


public void disabled_testFunctionReturn_arrow() { public void testFunctionReturn_arrow() {
Node fn = parse("(): string => 'hello';").getFirstFirstChild(); Node fn = parse("(): string => 'hello';").getFirstFirstChild();
assertDeclaredType("string type", stringType(), fn); assertDeclaredType("string type", stringType(), fn);
} }
Expand Down Expand Up @@ -314,7 +314,7 @@ public void testFunctionType() {
parse("var n: (p1: string, p2: number) => boolean;"); parse("var n: (p1: string, p2: number) => boolean;");
parse("var n: () => () => number;"); parse("var n: () => () => number;");
parse("var n: (p1: string) => {};"); parse("var n: (p1: string) => {};");
// parse("(number): () => number => number;"); parse("(number): () => number => number;");


Node ast = parse("var n: (p1: string, p2: number) => boolean[];"); Node ast = parse("var n: (p1: string, p2: number) => boolean[];");
TypeDeclarationNode function = (TypeDeclarationNode) TypeDeclarationNode function = (TypeDeclarationNode)
Expand Down Expand Up @@ -510,7 +510,7 @@ public void testGenericClass() {


public void testGenericFunction() { public void testGenericFunction() {
parse("function foo<T>() {\n}"); parse("function foo<T>() {\n}");
// parse("var x = <K, V>(p) => 3;"); parse("var x = <K, V>(p) => 3;");
parse("class Foo {\n f<T>() {\n }\n}"); parse("class Foo {\n f<T>() {\n }\n}");
parse("(function<T>() {\n})();"); parse("(function<T>() {\n})();");
parse("function* foo<T>() {\n}"); parse("function* foo<T>() {\n}");
Expand All @@ -522,13 +522,40 @@ public void testGenericFunction() {
expectErrors("Parse error. 'identifier' expected"); expectErrors("Parse error. 'identifier' expected");
parse("function foo<>() {\n}"); parse("function foo<>() {\n}");


expectErrors("Parse error. invalid location for generics");
parse("var x = <T>(5 + 7);");

// Typecasting, not supported yet. // Typecasting, not supported yet.
expectErrors("Parse error. primary expression expected"); expectErrors("Parse error. invalid location for generics");
parse("var x = <T>((p:T) => 3);"); parse("var x = <T>((p:T) => 3);");


testNotEs6Typed("function foo<T>() {}", "generics"); testNotEs6Typed("function foo<T>() {}", "generics");
} }


public void testColonTypeInArrowFunction() {
parse("(x: string) => 'hello';");
parse("(x: number = 1) => 'hello';");
parse("(x: string): string => 'hello';");
parse("(x: string, y: number): string => 'hello';");
parse("1 ? (x: number) => '2' : (y: boolean) => 3 ? 4 : 5;");
parse("var x = (y: number, z = (w: number) => 1) => 2;");
}

public void testInvalidColonType() {
expectErrors("Parse error. invalid location for colon type expression");
parse("var x = 3 : number;");
expectErrors("Parse error. invalid location for colon type expression");
parse("var x = (3 : number);");
expectErrors("Parse error. invalid location for colon type expression");
parse("var x = (x : number);");
expectErrors("Parse error. invalid location for colon type expression");
parse("var x = ((y): number);");
expectErrors("Parse error. invalid location for colon type expression");
parse("var x = ((y: string, x: number): number);");
expectErrors("Parse error. invalid arrow function parameters");
parse("var x = 3 : number => 4;");
}

public void testImplements() { public void testImplements() {
parse("class Foo implements Bar, Baz {\n}"); parse("class Foo implements Bar, Baz {\n}");
parse("class Foo extends Bar implements Baz {\n}"); parse("class Foo extends Bar implements Baz {\n}");
Expand Down

0 comments on commit 7a8706c

Please sign in to comment.