From 1cedab18be7ad82500f1521cdebf7ba1422e2845 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Thu, 19 Jul 2018 12:50:36 -0700 Subject: [PATCH] Fix parsing of parenthesized JSDoc parameters (#25799) * Fix parsing of parenthesized JSDoc parameters Parenthesis can start a jsdoc function parameter since it is just a type, and parenthesis can start a type: ```js /** @type {function(((string))): void} */ ``` However, this is not legal in other parameter lists: ```ts function x((((a))): string) { } ``` This change makes jsdoc function parameter lists parse differently than normal parameter lists by allowing parenthesis as a start character of jsdoc parameters. * Parse nested uses of jsdoc function types * Fix test --- src/compiler/parser.ts | 20 ++++++++++++++----- .../jsdocParseHigherOrderFunction.symbols | 9 +++++++++ .../jsdocParseHigherOrderFunction.types | 11 ++++++++++ ...ocParseParenthesizedJSDocParameter.symbols | 9 +++++++++ ...sdocParseParenthesizedJSDocParameter.types | 11 ++++++++++ .../jsdoc/jsdocParseHigherOrderFunction.ts | 7 +++++++ .../jsdocParseParenthesizedJSDocParameter.ts | 7 +++++++ 7 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 tests/baselines/reference/jsdocParseHigherOrderFunction.symbols create mode 100644 tests/baselines/reference/jsdocParseHigherOrderFunction.types create mode 100644 tests/baselines/reference/jsdocParseParenthesizedJSDocParameter.symbols create mode 100644 tests/baselines/reference/jsdocParseParenthesizedJSDocParameter.types create mode 100644 tests/cases/conformance/jsdoc/jsdocParseHigherOrderFunction.ts create mode 100644 tests/cases/conformance/jsdoc/jsdocParseParenthesizedJSDocParameter.ts diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 942a56bcc30..ba266d6b217 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -1509,7 +1509,9 @@ namespace ts { case ParsingContext.ArgumentExpressions: return token() === SyntaxKind.DotDotDotToken || isStartOfExpression(); case ParsingContext.Parameters: - return isStartOfParameter(); + return isStartOfParameter(/*isJSDocParameter*/ false); + case ParsingContext.JSDocParameters: + return isStartOfParameter(/*isJSDocParameter*/ true); case ParsingContext.TypeArguments: case ParsingContext.TupleElementTypes: return token() === SyntaxKind.CommaToken || isStartOfType(); @@ -1612,6 +1614,7 @@ namespace ts { case ParsingContext.TupleElementTypes: case ParsingContext.ArrayBindingElements: return token() === SyntaxKind.CloseBracketToken; + case ParsingContext.JSDocParameters: case ParsingContext.Parameters: case ParsingContext.RestProperties: // Tokens other than ')' and ']' (the latter for index signatures) are here for better error recovery @@ -1795,6 +1798,7 @@ namespace ts { case ParsingContext.VariableDeclarations: return isReusableVariableDeclaration(node); + case ParsingContext.JSDocParameters: case ParsingContext.Parameters: return isReusableParameter(node); @@ -2009,6 +2013,7 @@ namespace ts { case ParsingContext.ArgumentExpressions: return Diagnostics.Argument_expression_expected; case ParsingContext.ObjectLiteralMembers: return Diagnostics.Property_assignment_expected; case ParsingContext.ArrayLiteralMembers: return Diagnostics.Expression_or_comma_expected; + case ParsingContext.JSDocParameters: return Diagnostics.Parameter_declaration_expected; case ParsingContext.Parameters: return Diagnostics.Parameter_declaration_expected; case ParsingContext.TypeParameters: return Diagnostics.Type_parameter_declaration_expected; case ParsingContext.TypeArguments: return Diagnostics.Type_argument_expected; @@ -2430,12 +2435,12 @@ namespace ts { return undefined; } - function isStartOfParameter(): boolean { + function isStartOfParameter(isJSDocParameter: boolean): boolean { return token() === SyntaxKind.DotDotDotToken || isIdentifierOrPattern() || isModifierKind(token()) || token() === SyntaxKind.AtToken || - isStartOfType(/*inStartOfParameter*/ true); + isStartOfType(/*inStartOfParameter*/ !isJSDocParameter); } function parseParameter(): ParameterDeclaration { @@ -2534,7 +2539,9 @@ namespace ts { setYieldContext(!!(flags & SignatureFlags.Yield)); setAwaitContext(!!(flags & SignatureFlags.Await)); - signature.parameters = parseDelimitedList(ParsingContext.Parameters, flags & SignatureFlags.JSDoc ? parseJSDocParameter : parseParameter); + signature.parameters = flags & SignatureFlags.JSDoc ? + parseDelimitedList(ParsingContext.JSDocParameters, parseJSDocParameter) : + parseDelimitedList(ParsingContext.Parameters, parseParameter); setYieldContext(savedYieldContext); setAwaitContext(savedAwaitContext); @@ -2960,6 +2967,8 @@ namespace ts { case SyntaxKind.InferKeyword: case SyntaxKind.ImportKeyword: return true; + case SyntaxKind.FunctionKeyword: + return !inStartOfParameter; case SyntaxKind.MinusToken: return !inStartOfParameter && lookAhead(nextTokenIsNumericLiteral); case SyntaxKind.OpenParenToken: @@ -2973,7 +2982,7 @@ namespace ts { function isStartOfParenthesizedOrFunctionType() { nextToken(); - return token() === SyntaxKind.CloseParenToken || isStartOfParameter() || isStartOfType(); + return token() === SyntaxKind.CloseParenToken || isStartOfParameter(/*isJSDocParameter*/ false) || isStartOfType(); } function parsePostfixTypeOrHigher(): TypeNode { @@ -6255,6 +6264,7 @@ namespace ts { JsxChildren, // Things between opening and closing JSX tags ArrayLiteralMembers, // Members in array literal Parameters, // Parameters in parameter list + JSDocParameters, // JSDoc parameters in parameter list of JSDoc function type RestProperties, // Property names in a rest type list TypeParameters, // Type parameters in type parameter list TypeArguments, // Type arguments in type argument list diff --git a/tests/baselines/reference/jsdocParseHigherOrderFunction.symbols b/tests/baselines/reference/jsdocParseHigherOrderFunction.symbols new file mode 100644 index 00000000000..2876b162687 --- /dev/null +++ b/tests/baselines/reference/jsdocParseHigherOrderFunction.symbols @@ -0,0 +1,9 @@ +=== tests/cases/conformance/jsdoc/paren.js === +/** @type {function((string), function((string)): string): string} */ +var x = (s, id) => id(s) +>x : Symbol(x, Decl(paren.js, 1, 3)) +>s : Symbol(s, Decl(paren.js, 1, 9)) +>id : Symbol(id, Decl(paren.js, 1, 11)) +>id : Symbol(id, Decl(paren.js, 1, 11)) +>s : Symbol(s, Decl(paren.js, 1, 9)) + diff --git a/tests/baselines/reference/jsdocParseHigherOrderFunction.types b/tests/baselines/reference/jsdocParseHigherOrderFunction.types new file mode 100644 index 00000000000..08e4e0bc6a4 --- /dev/null +++ b/tests/baselines/reference/jsdocParseHigherOrderFunction.types @@ -0,0 +1,11 @@ +=== tests/cases/conformance/jsdoc/paren.js === +/** @type {function((string), function((string)): string): string} */ +var x = (s, id) => id(s) +>x : (arg0: string, arg1: (arg0: string) => string) => string +>(s, id) => id(s) : (s: string, id: (arg0: string) => string) => string +>s : string +>id : (arg0: string) => string +>id(s) : string +>id : (arg0: string) => string +>s : string + diff --git a/tests/baselines/reference/jsdocParseParenthesizedJSDocParameter.symbols b/tests/baselines/reference/jsdocParseParenthesizedJSDocParameter.symbols new file mode 100644 index 00000000000..b870f8b7240 --- /dev/null +++ b/tests/baselines/reference/jsdocParseParenthesizedJSDocParameter.symbols @@ -0,0 +1,9 @@ +=== tests/cases/conformance/jsdoc/paren.js === +/** @type {function((string)): string} */ +var x = s => s.toString() +>x : Symbol(x, Decl(paren.js, 1, 3)) +>s : Symbol(s, Decl(paren.js, 1, 7)) +>s.toString : Symbol(String.toString, Decl(lib.es5.d.ts, --, --)) +>s : Symbol(s, Decl(paren.js, 1, 7)) +>toString : Symbol(String.toString, Decl(lib.es5.d.ts, --, --)) + diff --git a/tests/baselines/reference/jsdocParseParenthesizedJSDocParameter.types b/tests/baselines/reference/jsdocParseParenthesizedJSDocParameter.types new file mode 100644 index 00000000000..aa5a5d477e6 --- /dev/null +++ b/tests/baselines/reference/jsdocParseParenthesizedJSDocParameter.types @@ -0,0 +1,11 @@ +=== tests/cases/conformance/jsdoc/paren.js === +/** @type {function((string)): string} */ +var x = s => s.toString() +>x : (arg0: string) => string +>s => s.toString() : (s: string) => string +>s : string +>s.toString() : string +>s.toString : () => string +>s : string +>toString : () => string + diff --git a/tests/cases/conformance/jsdoc/jsdocParseHigherOrderFunction.ts b/tests/cases/conformance/jsdoc/jsdocParseHigherOrderFunction.ts new file mode 100644 index 00000000000..f09bbea99ab --- /dev/null +++ b/tests/cases/conformance/jsdoc/jsdocParseHigherOrderFunction.ts @@ -0,0 +1,7 @@ +// @noemit: true +// @allowjs: true +// @checkjs: true +// @strict: true +// @Filename: paren.js +/** @type {function((string), function((string)): string): string} */ +var x = (s, id) => id(s) diff --git a/tests/cases/conformance/jsdoc/jsdocParseParenthesizedJSDocParameter.ts b/tests/cases/conformance/jsdoc/jsdocParseParenthesizedJSDocParameter.ts new file mode 100644 index 00000000000..216d0641120 --- /dev/null +++ b/tests/cases/conformance/jsdoc/jsdocParseParenthesizedJSDocParameter.ts @@ -0,0 +1,7 @@ +// @noemit: true +// @allowjs: true +// @checkjs: true +// @strict: true +// @Filename: paren.js +/** @type {function((string)): string} */ +var x = s => s.toString()