From 531d1ac73478b0748532e6124b1a88d2d3e39fec Mon Sep 17 00:00:00 2001 From: Linus Groh Date: Tue, 18 Jan 2022 23:46:16 +0000 Subject: [PATCH] LibJS: Capture source text of FunctionNode and ClassExpression --- Userland/Libraries/LibJS/AST.h | 18 +++++++---- Userland/Libraries/LibJS/Parser.cpp | 46 +++++++++++++++++++++-------- Userland/Libraries/LibJS/Parser.h | 2 +- 3 files changed, 46 insertions(+), 20 deletions(-) diff --git a/Userland/Libraries/LibJS/AST.h b/Userland/Libraries/LibJS/AST.h index cfbb18e97e..c1cc7ee2e3 100644 --- a/Userland/Libraries/LibJS/AST.h +++ b/Userland/Libraries/LibJS/AST.h @@ -484,6 +484,7 @@ public: }; FlyString const& name() const { return m_name; } + String const& source_text() const { return m_source_text; } Statement const& body() const { return *m_body; } Vector const& parameters() const { return m_parameters; }; i32 function_length() const { return m_function_length; } @@ -494,8 +495,9 @@ public: FunctionKind kind() const { return m_kind; } protected: - FunctionNode(FlyString name, NonnullRefPtr body, Vector parameters, i32 function_length, FunctionKind kind, bool is_strict_mode, bool might_need_arguments_object, bool contains_direct_call_to_eval, bool is_arrow_function) + FunctionNode(FlyString name, String source_text, NonnullRefPtr body, Vector parameters, i32 function_length, FunctionKind kind, bool is_strict_mode, bool might_need_arguments_object, bool contains_direct_call_to_eval, bool is_arrow_function) : m_name(move(name)) + , m_source_text(move(source_text)) , m_body(move(body)) , m_parameters(move(parameters)) , m_function_length(function_length) @@ -520,6 +522,7 @@ protected: private: FlyString m_name; + String m_source_text; NonnullRefPtr m_body; Vector const m_parameters; const i32 m_function_length; @@ -536,9 +539,9 @@ class FunctionDeclaration final public: static bool must_have_name() { return true; } - FunctionDeclaration(SourceRange source_range, FlyString const& name, NonnullRefPtr body, Vector parameters, i32 function_length, FunctionKind kind, bool is_strict_mode, bool might_need_arguments_object, bool contains_direct_call_to_eval) + FunctionDeclaration(SourceRange source_range, FlyString const& name, String source_text, NonnullRefPtr body, Vector parameters, i32 function_length, FunctionKind kind, bool is_strict_mode, bool might_need_arguments_object, bool contains_direct_call_to_eval) : Declaration(source_range) - , FunctionNode(name, move(body), move(parameters), function_length, kind, is_strict_mode, might_need_arguments_object, contains_direct_call_to_eval, false) + , FunctionNode(name, move(source_text), move(body), move(parameters), function_length, kind, is_strict_mode, might_need_arguments_object, contains_direct_call_to_eval, false) { } @@ -562,9 +565,9 @@ class FunctionExpression final public: static bool must_have_name() { return false; } - FunctionExpression(SourceRange source_range, FlyString const& name, NonnullRefPtr body, Vector parameters, i32 function_length, FunctionKind kind, bool is_strict_mode, bool might_need_arguments_object, bool contains_direct_call_to_eval, bool is_arrow_function = false) + FunctionExpression(SourceRange source_range, FlyString const& name, String source_text, NonnullRefPtr body, Vector parameters, i32 function_length, FunctionKind kind, bool is_strict_mode, bool might_need_arguments_object, bool contains_direct_call_to_eval, bool is_arrow_function = false) : Expression(source_range) - , FunctionNode(name, move(body), move(parameters), function_length, kind, is_strict_mode, might_need_arguments_object, contains_direct_call_to_eval, is_arrow_function) + , FunctionNode(name, move(source_text), move(body), move(parameters), function_length, kind, is_strict_mode, might_need_arguments_object, contains_direct_call_to_eval, is_arrow_function) { } @@ -1232,9 +1235,10 @@ public: class ClassExpression final : public Expression { public: - ClassExpression(SourceRange source_range, String name, RefPtr constructor, RefPtr super_class, NonnullRefPtrVector elements) + ClassExpression(SourceRange source_range, String name, String source_text, RefPtr constructor, RefPtr super_class, NonnullRefPtrVector elements) : Expression(source_range) , m_name(move(name)) + , m_source_text(move(source_text)) , m_constructor(move(constructor)) , m_super_class(move(super_class)) , m_elements(move(elements)) @@ -1242,6 +1246,7 @@ public: } StringView name() const { return m_name; } + String const& source_text() const { return m_source_text; } RefPtr constructor() const { return m_constructor; } virtual Completion execute(Interpreter&, GlobalObject&) const override; @@ -1255,6 +1260,7 @@ private: virtual bool is_class_expression() const override { return true; } String m_name; + String m_source_text; RefPtr m_constructor; RefPtr m_super_class; NonnullRefPtrVector m_elements; diff --git a/Userland/Libraries/LibJS/Parser.cpp b/Userland/Libraries/LibJS/Parser.cpp index 9b6e353c2f..a6b5284fb1 100644 --- a/Userland/Libraries/LibJS/Parser.cpp +++ b/Userland/Libraries/LibJS/Parser.cpp @@ -702,7 +702,11 @@ RefPtr Parser::try_parse_arrow_function_expression(bool expe } save_state(); - auto rule_start = push_start(); + auto rule_start = (expect_parens && !is_async) + // Someone has consumed the opening parenthesis for us! Start there. + ? RulePosition { *this, m_rule_starts.last() } + // We've not encountered one yet, so the rule start is actually here. + : push_start(); ArmedScopeGuard state_rollback_guard = [&] { load_state(); @@ -819,9 +823,12 @@ RefPtr Parser::try_parse_arrow_function_expression(bool expe } } + auto function_start_offset = rule_start.position().offset; + auto function_end_offset = position().offset - m_state.current_token.trivia().length(); + auto source_text = String { m_state.lexer.source().substring_view(function_start_offset, function_end_offset - function_start_offset) }; return create_ast_node( - { m_state.current_token.filename(), rule_start.position(), position() }, "", move(body), - move(parameters), function_length, function_kind, body->in_strict_mode(), + { m_state.current_token.filename(), rule_start.position(), position() }, "", move(source_text), + move(body), move(parameters), function_length, function_kind, body->in_strict_mode(), /* might_need_arguments_object */ false, contains_direct_call_to_eval, /* is_arrow_function */ true); } @@ -1066,6 +1073,8 @@ NonnullRefPtr Parser::parse_class_expression(bool expect_class_ continue; } + auto function_start = position(); + if (match(TokenType::Async)) { auto lookahead_token = next_token(); if (lookahead_token.type() != TokenType::Semicolon && lookahead_token.type() != TokenType::CurlyClose @@ -1086,6 +1095,7 @@ NonnullRefPtr Parser::parse_class_expression(bool expect_class_ if (match(TokenType::Identifier)) { consume(); is_static = true; + function_start = position(); if (match(TokenType::Async)) { consume(); is_async = true; @@ -1242,7 +1252,7 @@ NonnullRefPtr Parser::parse_class_expression(bool expect_class_ parse_options |= FunctionNodeParseOptions::IsGeneratorFunction; if (is_async) parse_options |= FunctionNodeParseOptions::IsAsyncFunction; - auto function = parse_function_node(parse_options); + auto function = parse_function_node(parse_options, function_start); if (is_constructor) { constructor = move(function); } else if (!property_key.is_null()) { @@ -1295,13 +1305,13 @@ NonnullRefPtr Parser::parse_class_expression(bool expect_class_ constructor_body->append(create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, move(super_call))); constructor = create_ast_node( - { m_state.current_token.filename(), rule_start.position(), position() }, class_name, move(constructor_body), - Vector { FunctionNode::Parameter { FlyString { "args" }, nullptr, true } }, 0, FunctionKind::Normal, + { m_state.current_token.filename(), rule_start.position(), position() }, class_name, "", + move(constructor_body), Vector { FunctionNode::Parameter { FlyString { "args" }, nullptr, true } }, 0, FunctionKind::Normal, /* is_strict_mode */ true, /* might_need_arguments_object */ false, /* contains_direct_call_to_eval */ false); } else { constructor = create_ast_node( - { m_state.current_token.filename(), rule_start.position(), position() }, class_name, move(constructor_body), - Vector {}, 0, FunctionKind::Normal, + { m_state.current_token.filename(), rule_start.position(), position() }, class_name, "", + move(constructor_body), Vector {}, 0, FunctionKind::Normal, /* is_strict_mode */ true, /* might_need_arguments_object */ false, /* contains_direct_call_to_eval */ false); } } @@ -1316,7 +1326,11 @@ NonnullRefPtr Parser::parse_class_expression(bool expect_class_ syntax_error(String::formatted("Reference to undeclared private field or method '{}'", private_name)); } - return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, move(class_name), move(constructor), move(super_class), move(elements)); + auto function_start_offset = rule_start.position().offset; + auto function_end_offset = position().offset - m_state.current_token.trivia().length(); + auto source_text = String { m_state.lexer.source().substring_view(function_start_offset, function_end_offset - function_start_offset) }; + + return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, move(class_name), move(source_text), move(constructor), move(super_class), move(elements)); } Parser::PrimaryExpressionParseResult Parser::parse_primary_expression() @@ -1640,6 +1654,7 @@ NonnullRefPtr Parser::parse_object_expression() } auto type = m_state.current_token.type(); + auto function_start = position(); if (match(TokenType::Async)) { auto lookahead_token = next_token(); @@ -1701,7 +1716,7 @@ NonnullRefPtr Parser::parse_object_expression() parse_options |= FunctionNodeParseOptions::IsGeneratorFunction; if (function_kind == FunctionKind::Async || function_kind == FunctionKind::AsyncGenerator) parse_options |= FunctionNodeParseOptions::IsAsyncFunction; - auto function = parse_function_node(parse_options); + auto function = parse_function_node(parse_options, function_start); properties.append(create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, *property_name, function, property_type, true)); } else if (match(TokenType::Colon)) { if (!property_name) { @@ -2451,9 +2466,11 @@ NonnullRefPtr Parser::parse_block_statement() } template -NonnullRefPtr Parser::parse_function_node(u8 parse_options) +NonnullRefPtr Parser::parse_function_node(u8 parse_options, Optional const& function_start) { - auto rule_start = push_start(); + auto rule_start = function_start.has_value() + ? RulePosition { *this, *function_start } + : push_start(); VERIFY(!(parse_options & FunctionNodeParseOptions::IsGetterFunction && parse_options & FunctionNodeParseOptions::IsSetterFunction)); TemporaryChange super_property_access_rollback(m_state.allow_super_property_lookup, !!(parse_options & FunctionNodeParseOptions::AllowSuperPropertyLookup)); @@ -2529,9 +2546,12 @@ NonnullRefPtr Parser::parse_function_node(u8 parse_options) if (has_strict_directive) check_identifier_name_for_assignment_validity(name, true); + auto function_start_offset = rule_start.position().offset; + auto function_end_offset = position().offset - m_state.current_token.trivia().length(); + auto source_text = String { m_state.lexer.source().substring_view(function_start_offset, function_end_offset - function_start_offset) }; return create_ast_node( { m_state.current_token.filename(), rule_start.position(), position() }, - name, move(body), move(parameters), function_length, + name, move(source_text), move(body), move(parameters), function_length, function_kind, has_strict_directive, m_state.function_might_need_arguments_object, contains_direct_call_to_eval); } diff --git a/Userland/Libraries/LibJS/Parser.h b/Userland/Libraries/LibJS/Parser.h index 4f601af044..cfeb29e31d 100644 --- a/Userland/Libraries/LibJS/Parser.h +++ b/Userland/Libraries/LibJS/Parser.h @@ -45,7 +45,7 @@ public: NonnullRefPtr parse_program(bool starts_in_strict_mode = false); template - NonnullRefPtr parse_function_node(u8 parse_options = FunctionNodeParseOptions::CheckForFunctionAndName); + NonnullRefPtr parse_function_node(u8 parse_options = FunctionNodeParseOptions::CheckForFunctionAndName, Optional const& function_start = {}); Vector parse_formal_parameters(int& function_length, u8 parse_options = 0); enum class AllowDuplicates {