From 04faa3f7eb39fd1c514967252852f1720b92b5e2 Mon Sep 17 00:00:00 2001 From: Seonggun Kim Date: Thu, 26 Oct 2023 07:27:50 -0700 Subject: [PATCH] Parse this argument in TypeScript function. (#1173) Summary: Parse `this` argument in TypeScript function. Pull Request resolved: https://github.com/facebook/hermes/pull/1173 Test Plan: Unit tests added. Reviewed By: neildhar Differential Revision: D50666819 Pulled By: avp fbshipit-source-id: 1d61361605e5530ee91cf6105a2cc3e85cbb82af --- lib/Parser/JSParserImpl-ts.cpp | 22 +++++++ lib/Parser/JSParserImpl.cpp | 8 +-- test/Parser/ts/function-alias.ts | 26 ++++++++ test/Parser/ts/function-this-error.ts | 13 ++++ test/Parser/ts/function.ts | 85 +++++++++++++++++++++++++++ 5 files changed, 150 insertions(+), 4 deletions(-) create mode 100644 test/Parser/ts/function-this-error.ts create mode 100644 test/Parser/ts/function.ts diff --git a/lib/Parser/JSParserImpl-ts.cpp b/lib/Parser/JSParserImpl-ts.cpp index b80c46f50e5..7be6eb47dc2 100644 --- a/lib/Parser/JSParserImpl-ts.cpp +++ b/lib/Parser/JSParserImpl-ts.cpp @@ -240,6 +240,28 @@ Optional JSParserImpl::parseTSFunctionOrParenthesizedType( ESTree::Node *type = nullptr; ESTree::NodeList params{}; + if (check(TokenKind::rw_this)) { + OptValue optNext = lexer_.lookahead1(None); + if (optNext.hasValue() && *optNext == TokenKind::colon) { + SMLoc thisStart = advance(JSLexer::GrammarContext::Type).Start; + advance(JSLexer::GrammarContext::Type); + CHECK_RECURSION; + auto typeAnnotation = parseTypeAnnotationTS(); + if (!typeAnnotation) + return None; + + params.push_back(*setLocation( + thisStart, + getPrevTokenEndLoc(), + new (context_) ESTree::IdentifierNode( + thisIdent_, *typeAnnotation, /* optional */ false))); + checkAndEat(TokenKind::comma, JSLexer::GrammarContext::Type); + } else if (optNext.hasValue() && *optNext == TokenKind::question) { + error(tok_->getSourceRange(), "'this' constraint may not be optional"); + return None; + } + } + if (allowAnonFunctionType_ && checkAndEat(TokenKind::dotdotdot, JSLexer::GrammarContext::Type)) { isFunction = true; diff --git a/lib/Parser/JSParserImpl.cpp b/lib/Parser/JSParserImpl.cpp index 19815d794a5..3c61b1f250b 100644 --- a/lib/Parser/JSParserImpl.cpp +++ b/lib/Parser/JSParserImpl.cpp @@ -590,9 +590,9 @@ bool JSParserImpl::parseFormalParameters( // ( SMLoc lparenLoc = advance().Start; -#if HERMES_PARSE_FLOW - // The first parameter can be 'this' in Flow mode. - if (context_.getParseFlow() && check(TokenKind::rw_this)) { +#if HERMES_PARSE_FLOW || HERMES_PARSE_TS + // The first parameter can be 'this' in Flow and TypeScript. + if (context_.getParseTypes() && check(TokenKind::rw_this)) { auto *name = tok_->getResWordIdentifier(); SMLoc thisParamStart = advance().Start; @@ -605,7 +605,7 @@ bool JSParserImpl::parseFormalParameters( thisParamStart)) return false; - auto optType = parseTypeAnnotationFlow(annotStart); + auto optType = parseTypeAnnotation(annotStart); if (!optType) return false; ESTree::Node *type = *optType; diff --git a/test/Parser/ts/function-alias.ts b/test/Parser/ts/function-alias.ts index f3c49649f76..77300f4ac91 100644 --- a/test/Parser/ts/function-alias.ts +++ b/test/Parser/ts/function-alias.ts @@ -291,6 +291,32 @@ type A = ({x}: number) => number; // CHECK-NEXT: }, // CHECK-NEXT: "typeParameters": null // CHECK-NEXT: } +// CHECK-NEXT: }, + +type A = (this: string) => void; +// CHECK-NEXT: { +// CHECK-NEXT: "type": "TSTypeAliasDeclaration", +// CHECK-NEXT: "id": { +// CHECK-NEXT: "type": "Identifier", +// CHECK-NEXT: "name": "A" +// CHECK-NEXT: }, +// CHECK-NEXT: "typeParameters": null, +// CHECK-NEXT: "typeAnnotation": { +// CHECK-NEXT: "type": "TSFunctionType", +// CHECK-NEXT: "params": [ +// CHECK-NEXT: { +// CHECK-NEXT: "type": "Identifier", +// CHECK-NEXT: "name": "this", +// CHECK-NEXT: "typeAnnotation": { +// CHECK-NEXT: "type": "TSStringKeyword" +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "returnType": { +// CHECK-NEXT: "type": "TSVoidKeyword" +// CHECK-NEXT: }, +// CHECK-NEXT: "typeParameters": null +// CHECK-NEXT: } // CHECK-NEXT: } // CHECK-NEXT: ] diff --git a/test/Parser/ts/function-this-error.ts b/test/Parser/ts/function-this-error.ts new file mode 100644 index 00000000000..4388ee56c69 --- /dev/null +++ b/test/Parser/ts/function-this-error.ts @@ -0,0 +1,13 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +// RUN: (! %hermes -parse-ts -dump-ast -pretty-json %s 2>&1) | %FileCheck %s --match-full-lines + +type A = (this?: string) => void; +// CHECK: {{.*}}:10:11: error: 'this' constraint may not be optional +// CHECK-NEXT: type A = (this?: string) => void; +// CHECK-NEXT: ^~~~ diff --git a/test/Parser/ts/function.ts b/test/Parser/ts/function.ts new file mode 100644 index 00000000000..ebaa0cfa7da --- /dev/null +++ b/test/Parser/ts/function.ts @@ -0,0 +1,85 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +// RUN: %hermesc -parse-ts -dump-ast -pretty-json %s | %FileCheck %s --match-full-lines + +// CHECK-LABEL: { +// CHECK-NEXT: "type": "Program", +// CHECK-NEXT: "body": [ + +function foo(this: string, x: boolean, y?: number, ...args: string[]): void {} +// CHECK-NEXT: { +// CHECK-NEXT: "type": "FunctionDeclaration", +// CHECK-NEXT: "id": { +// CHECK-NEXT: "type": "Identifier", +// CHECK-NEXT: "name": "foo" +// CHECK-NEXT: }, +// CHECK-NEXT: "params": [ +// CHECK-NEXT: { +// CHECK-NEXT: "type": "Identifier", +// CHECK-NEXT: "name": "this", +// CHECK-NEXT: "typeAnnotation": { +// CHECK-NEXT: "type": "TSTypeAnnotation", +// CHECK-NEXT: "typeAnnotation": { +// CHECK-NEXT: "type": "TSStringKeyword" +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "type": "Identifier", +// CHECK-NEXT: "name": "x", +// CHECK-NEXT: "typeAnnotation": { +// CHECK-NEXT: "type": "TSTypeAnnotation", +// CHECK-NEXT: "typeAnnotation": { +// CHECK-NEXT: "type": "TSBooleanKeyword" +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "type": "Identifier", +// CHECK-NEXT: "name": "y", +// CHECK-NEXT: "typeAnnotation": { +// CHECK-NEXT: "type": "TSTypeAnnotation", +// CHECK-NEXT: "typeAnnotation": { +// CHECK-NEXT: "type": "TSNumberKeyword" +// CHECK-NEXT: } +// CHECK-NEXT: }, +// CHECK-NEXT: "optional": true +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "type": "RestElement", +// CHECK-NEXT: "argument": { +// CHECK-NEXT: "type": "Identifier", +// CHECK-NEXT: "name": "args", +// CHECK-NEXT: "typeAnnotation": { +// CHECK-NEXT: "type": "TSTypeAnnotation", +// CHECK-NEXT: "typeAnnotation": { +// CHECK-NEXT: "type": "TSArrayType", +// CHECK-NEXT: "elementType": { +// CHECK-NEXT: "type": "TSStringKeyword" +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "body": { +// CHECK-NEXT: "type": "BlockStatement", +// CHECK-NEXT: "body": [] +// CHECK-NEXT: }, +// CHECK-NEXT: "returnType": { +// CHECK-NEXT: "type": "TSTypeAnnotation", +// CHECK-NEXT: "typeAnnotation": { +// CHECK-NEXT: "type": "TSVoidKeyword" +// CHECK-NEXT: } +// CHECK-NEXT: }, +// CHECK-NEXT: "generator": false, +// CHECK-NEXT: "async": false +// CHECK-NEXT: } + +// CHECK-NEXT: ] +// CHECK-NEXT: }