diff --git a/src/ast.js b/src/ast.js index 0a3b05b43..4ab5de30d 100644 --- a/src/ast.js +++ b/src/ast.js @@ -517,6 +517,7 @@ AST.prototype.checkNodes = function () { require("./ast/method"), require("./ast/name"), require("./ast/namespace"), + require("./ast/namedargument"), require("./ast/new"), require("./ast/node"), require("./ast/noop"), diff --git a/src/ast/namedargument.js b/src/ast/namedargument.js new file mode 100644 index 000000000..c960c0f26 --- /dev/null +++ b/src/ast/namedargument.js @@ -0,0 +1,28 @@ +/** + * Copyright (C) 2018 Glayzzle (BSD3 License) + * @authors https://github.com/glayzzle/php-parser/graphs/contributors + * @url http://glayzzle.com + */ +"use strict"; + +const Expression = require("./expression"); +const KIND = "namedargument"; + +/** + * Named arguments. + * @constructor namedargument + * @extends {Expression} + * @property {String} name + * @property {Expression} value + * @see https://www.php.net/manual/en/functions.arguments.php#functions.named-arguments + */ +module.exports = Expression.extends(KIND, function namedargument( + name, + value, + docs, + location +) { + Expression.apply(this, [KIND, docs, location]); + this.name = name; + this.value = value; +}); diff --git a/src/parser/function.js b/src/parser/function.js index f4ef967a4..c05260e59 100644 --- a/src/parser/function.js +++ b/src/parser/function.js @@ -283,13 +283,26 @@ module.exports = { }, /** * ```ebnf - * argument_list ::= T_ELLIPSIS? expr + * argument_list ::= T_STRING ':' expr | T_ELLIPSIS? expr * ``` */ read_argument: function () { if (this.token === this.tok.T_ELLIPSIS) { return this.node("variadic")(this.next().read_expr()); } + if ( + this.token === this.tok.T_STRING || + Object.values(this.lexer.keywords).includes(this.token) + ) { + const backup = [this.token, this.lexer.getState()]; + const name = this.text(); + this.next(); + if (this.token === ":") { + return this.node("namedargument")(name, this.next().read_expr()); + } + this.lexer.tokens.push(backup); + this.next(); + } return this.read_expr(); }, /** diff --git a/test/snapshot/__snapshots__/call.test.js.snap b/test/snapshot/__snapshots__/call.test.js.snap index b3ffe948a..94f4448d8 100644 --- a/test/snapshot/__snapshots__/call.test.js.snap +++ b/test/snapshot/__snapshots__/call.test.js.snap @@ -97,6 +97,84 @@ Program { } `; +exports[`Test call comments 1`] = ` +Program { + "children": Array [ + ExpressionStatement { + "expression": Call { + "arguments": Array [ + Array { + "items": Array [ + Entry { + "byRef": false, + "key": null, + "kind": "entry", + "leadingComments": Array [ + CommentLine { + "kind": "commentline", + "offset": 10, + "value": "// comment +", + }, + ], + "unpack": false, + "value": Number { + "kind": "number", + "value": "100", + }, + }, + Entry { + "byRef": false, + "key": null, + "kind": "entry", + "unpack": false, + "value": Number { + "kind": "number", + "value": "0", + }, + }, + ], + "kind": "array", + "leadingComments": Array [ + CommentLine { + "kind": "commentline", + "offset": 10, + "value": "// comment +", + }, + ], + "shortForm": false, + }, + ], + "kind": "call", + "what": Name { + "kind": "name", + "name": "foo", + "resolution": "uqn", + }, + }, + "kind": "expressionstatement", + }, + ], + "comments": Array [ + CommentLine { + "kind": "commentline", + "offset": 10, + "value": "// comment +", + }, + CommentLine { + "kind": "commentline", + "offset": 10, + "value": "// comment +", + }, + ], + "errors": Array [], + "kind": "program", +} +`; + exports[`Test call inside offsetlookup 1`] = ` Program { "children": Array [ @@ -636,6 +714,79 @@ Program { } `; +exports[`Test call keyword as named argument 1`] = ` +Program { + "children": Array [ + ExpressionStatement { + "expression": Call { + "arguments": Array [ + namedargument { + "kind": "namedargument", + "name": "array", + "value": Variable { + "curly": false, + "kind": "variable", + "name": "a", + }, + }, + ], + "kind": "call", + "what": Name { + "kind": "name", + "name": "foo", + "resolution": "uqn", + }, + }, + "kind": "expressionstatement", + }, + ], + "errors": Array [], + "kind": "program", +} +`; + +exports[`Test call mix of unnamed and named arguments 1`] = ` +Program { + "children": Array [ + ExpressionStatement { + "expression": Call { + "arguments": Array [ + Number { + "kind": "number", + "value": "50", + }, + namedargument { + "kind": "namedargument", + "name": "num", + "value": Number { + "kind": "number", + "value": "100", + }, + }, + namedargument { + "kind": "namedargument", + "name": "start_index", + "value": Number { + "kind": "number", + "value": "0", + }, + }, + ], + "kind": "call", + "what": Name { + "kind": "name", + "name": "foo", + "resolution": "uqn", + }, + }, + "kind": "expressionstatement", + }, + ], + "errors": Array [], + "kind": "program", +} +`; + exports[`Test call multiple (2) 1`] = ` Program { "children": Array [ @@ -752,6 +903,37 @@ Program { } `; +exports[`Test call named arguments in php 8.0 1`] = ` +Program { + "children": Array [ + ExpressionStatement { + "expression": Call { + "arguments": Array [ + namedargument { + "kind": "namedargument", + "name": "a", + "value": Variable { + "curly": false, + "kind": "variable", + "name": "a", + }, + }, + ], + "kind": "call", + "what": Name { + "kind": "name", + "name": "foo", + "resolution": "uqn", + }, + }, + "kind": "expressionstatement", + }, + ], + "errors": Array [], + "kind": "program", +} +`; + exports[`Test call nested 1`] = ` Program { "children": Array [ diff --git a/test/snapshot/call.test.js b/test/snapshot/call.test.js index cd35318d3..6df0e5e2f 100644 --- a/test/snapshot/call.test.js +++ b/test/snapshot/call.test.js @@ -244,4 +244,44 @@ describe("Test call", function () { }); expect(ast).toMatchSnapshot(); }); + it("named arguments in php 8.0", function () { + const astErr = parser.parseEval(`foo(a: $a);`, { + parser: { + version: "8.0", + debug: false, + }, + }); + expect(astErr).toMatchSnapshot(); + }); + it("keyword as named argument", function () { + const astErr = parser.parseEval(`foo(array: $a);`, { + parser: { + version: "8.0", + debug: false, + }, + }); + expect(astErr).toMatchSnapshot(); + }); + it("mix of unnamed and named arguments", function () { + const astErr = parser.parseEval(`foo(50, num: 100, start_index: 0);`, { + parser: { + version: "8.0", + debug: false, + }, + }); + expect(astErr).toMatchSnapshot(); + }); + it("comments", function () { + const astErr = parser.parseEval( + `foo(array // comment + (100, 0));`, + { + parser: { + extractDoc: true, + debug: false, + }, + } + ); + expect(astErr).toMatchSnapshot(); + }); });