Repository: glayzzle/php-parser
Pull request #92 (2017-10-08 00:00:21)

Added reserved word support for method and constant names
This covers all reserved word as names in class/trait methods and constants.


Patch:
diff --git a/src/parser.js b/src/parser.js
index 02f5554a..e166aa6b 100644
--- a/src/parser.js
+++ b/src/parser.js
@@ -33,6 +33,81 @@ var parser = function(lexer, ast) {
   this.extractDoc = false;
   this.suppressErrors = false;
   this.entries = {
+    'IDENTIFIER': [
+      this.tok.T_CLASS_C,
+      this.tok.T_DIR,
+      this.tok.T_FILE,
+      this.tok.T_FUNC_C,
+      this.tok.T_LINE,
+      this.tok.T_METHOD_C,
+      this.tok.T_NS_C,
+      this.tok.T_TRAIT,
+      this.tok.T_ABSTRACT,
+      this.tok.T_LOGICAL_AND,
+      this.tok.T_ARRAY,
+      this.tok.T_AS,
+      this.tok.T_BREAK,
+      this.tok.T_CALLABLE,
+      this.tok.T_CASE,
+      this.tok.T_CATCH,
+      this.tok.T_CLASS,
+      this.tok.T_CLONE,
+      this.tok.T_CONST,
+      this.tok.T_CONTINUE,
+      this.tok.T_DECLARE,
+      this.tok.T_DEFAULT,
+      this.tok.T_DO,
+      this.tok.T_ECHO,
+      this.tok.T_ELSE,
+      this.tok.T_ELSEIF,
+      this.tok.T_EMPTY,
+      this.tok.T_ENDDECLARE,
+      this.tok.T_ENDFOR,
+      this.tok.T_ENDFOREACH,
+      this.tok.T_ENDIF,
+      this.tok.T_ENDSWITCH,
+      this.tok.T_ENDWHILE,
+      this.tok.T_EVAL,
+      this.tok.T_EXIT,
+      this.tok.T_EXTENDS,
+      this.tok.T_FINAL,
+      this.tok.T_FINALLY,
+      this.tok.T_FOR,
+      this.tok.T_FOREACH,
+      this.tok.T_FUNCTION,
+      this.tok.T_GLOBAL,
+      this.tok.T_GOTO,
+      this.tok.T_IF,
+      this.tok.T_IMPLEMENTS,
+      this.tok.T_INCLUDE,
+      this.tok.T_INCLUDE_ONCE,
+      this.tok.T_INSTANCEOF,
+      this.tok.T_INSTEADOF,
+      this.tok.T_INTERFACE,
+      this.tok.T_ISSET,
+      this.tok.T_LIST,
+      this.tok.T_NAMESPACE,
+      this.tok.T_NEW,
+      this.tok.T_LOGICAL_OR,
+      this.tok.T_PRINT,
+      this.tok.T_PRIVATE,
+      this.tok.T_PROTECTED,
+      this.tok.T_PUBLIC,
+      this.tok.T_REQUIRE,
+      this.tok.T_REQUIRE_ONCE,
+      this.tok.T_RETURN,
+      this.tok.T_STATIC,
+      this.tok.T_SWITCH,
+      this.tok.T_THROW,
+      this.tok.T_TRAIT,
+      this.tok.T_TRY,
+      this.tok.T_UNSET,
+      this.tok.T_USE,
+      this.tok.T_VAR,
+      this.tok.T_WHILE,
+      this.tok.T_LOGICAL_XOR,
+      this.tok.T_YIELD
+    ],
     'VARIABLE': [
       this.tok.T_VARIABLE,
       '$', '&',
diff --git a/src/parser/class.js b/src/parser/class.js
index a294d65c..c6977509 100644
--- a/src/parser/class.js
+++ b/src/parser/class.js
@@ -183,7 +183,7 @@ module.exports = {
          */
         function read_constant_declaration() {
           var result = this.node('classconstant'), name = null, value = null;
-          if (this.expect(this.tok.T_STRING)) {
+          if (this.is('IDENTIFIER') || this.expect(this.tok.T_STRING)) {
             name = this.text();
             this.next();
           }
@@ -395,17 +395,26 @@ module.exports = {
   ,read_trait_use_alias: function() {
     var node = this.node();
     var trait = null;
-    var method = this.read_namespace_name();
+    var method;
 
-    if (this.token === this.tok.T_DOUBLE_COLON) {
-      if (this.next().expect(this.tok.T_STRING)) {
-        trait = method;
-        method = this.text();
+    if(this.is('IDENTIFIER')) {
+      method = this.text();
+      this.next();
+    } else {
+      method = this.read_namespace_name();
+
+      if (this.token === this.tok.T_DOUBLE_COLON) {
         this.next();
+
+        if (this.is('IDENTIFIER') || this.expect(this.tok.T_STRING)) {
+          trait = method;
+          method = this.text();
+          this.next();
+        }
+      } else {
+        // convert identifier as string
+        method = method.name;
       }
-    } else {
-      // convert identifier as string
-      method = method.name;
     }
 
     // handle trait precedence
@@ -424,8 +433,8 @@ module.exports = {
       if (this.next().is('T_MEMBER_FLAGS')) {
         flags = this.read_member_flags();
       }
-
-      if (this.token === this.tok.T_STRING) {
+      
+      if (this.is('IDENTIFIER') || this.token === this.tok.T_STRING) {
         alias = this.text();
         this.next();
       } else if (flags === false) {
diff --git a/src/parser/function.js b/src/parser/function.js
index 2d71de18..ec29a32a 100644
--- a/src/parser/function.js
+++ b/src/parser/function.js
@@ -76,9 +76,11 @@ module.exports = {
     var isRef = this.is_reference();
     var name = false, use = [], returnType = null, nullable = false;
     if (type !== 1) {
-      if (this.expect(this.tok.T_STRING)) {
+      if ((type === 2 && this.is('IDENTIFIER')) || this.expect(this.tok.T_STRING)) {
         name = this.text();
         this.next();
+      } else {
+        this.next();
       }
     }
     if (this.expect('(')) this.next();
diff --git a/src/parser/variable.js b/src/parser/variable.js
index 30506cad..1379fece 100644
--- a/src/parser/variable.js
+++ b/src/parser/variable.js
@@ -76,7 +76,7 @@ module.exports = {
     if (this.next().is([this.tok.T_VARIABLE, '$'])) {
       offset = this.read_reference_variable(encapsed, false);
     } else if (
-      this.token === this.tok.T_STRING
+      this.is('IDENTIFIER') || this.token === this.tok.T_STRING
       || this.token === this.tok.T_CLASS
     ) {
       offset = this.node('constref');
@@ -126,6 +126,7 @@ module.exports = {
           }
           result = node(result, offset);
           break;
+        
         case this.tok.T_OBJECT_OPERATOR:
           var node = this.node('propertylookup');
           var what = null;


Test patch:
diff --git a/test/classTests.js b/test/classTests.js
index 609962d8..fcd99942 100644
--- a/test/classTests.js
+++ b/test/classTests.js
@@ -12,6 +12,10 @@ describe('Test classes', function() {
       '  public function __construct(array $data = null) {',
       '    $this->data = $data;',
       '  }',
+      '  const list = "bar";',
+      '  public function new($foo = self::list) {',
+      '    return $this::list;',
+      '  }',
       '}',
       'abstract class bar {',
       '  use A, B {',
@@ -20,6 +24,8 @@ describe('Test classes', function() {
       '    B::bigTalk as public talk;',
       '    B::bigTalk as protected talk;',
       '    B::bigTalk as private talk;',
+      '    A::new as list;',
+      '    list as new;',
       '  }',
       '  /**',
       '   * Some informations',
@@ -81,7 +87,7 @@ describe('Test classes', function() {
       use.traits[0].name.should.be.exactly('A');
       use.traits[1].name.should.be.exactly('B');
       // test adaptations
-      use.adaptations.length.should.be.exactly(5);
+      use.adaptations.length.should.be.exactly(7);
       use.adaptations[0].kind.should.be.exactly('traitprecedence');
       use.adaptations[1].kind.should.be.exactly('traitprecedence');
       use.adaptations[2].kind.should.be.exactly('traitalias');
@@ -104,7 +110,11 @@ describe('Test classes', function() {
       use.adaptations[2].visibility.should.be.exactly('public');
       use.adaptations[3].visibility.should.be.exactly('protected');
       use.adaptations[4].visibility.should.be.exactly('private');
-
+      use.adaptations[5].trait.name.should.be.exactly('A');
+      use.adaptations[5].method.should.be.exactly('new');
+      use.adaptations[5].as.should.be.exactly('list');
+      use.adaptations[6].method.should.be.exactly('list');
+      use.adaptations[6].as.should.be.exactly('new');
     });
 
     it('test methods', function() {
@@ -123,6 +133,29 @@ describe('Test classes', function() {
       // check return type
       method.type.name.should.be.exactly('bar');
     });
+    
+    it('test reserved words', function() {
+      var constant = ast.children[0].body[3];
+      var method = ast.children[0].body[4];
+
+      constant.kind.should.be.exactly('classconstant');
+      constant.name.should.be.exactly('list');
+      constant.value.value.should.be.exactly('bar');
+
+      method.kind.should.be.exactly('method');
+      method.name.should.be.exactly('new');
+      method.arguments.length.should.be.exactly(1);
+      method.arguments[0].name.should.be.exactly('foo');
+      method.arguments[0].value.kind.should.be.exactly('staticlookup');
+      method.arguments[0].value.what.name.should.be.exactly('self');
+      method.arguments[0].value.offset.name.should.be.exactly('list');
+      method.body.children[0].kind.should.be.exactly('return');
+      method.body.children[0].expr.kind.should.be.exactly('staticlookup');
+      method.body.children[0].expr.what.kind.should.be.exactly('variable');
+      method.body.children[0].expr.what.name.should.be.exactly('this');
+      method.body.children[0].expr.offset.name.should.be.exactly('list');
+    });
+    
   });
 
   describe('Advanced tests', function() {
diff --git a/test/functionTests.js b/test/functionTests.js
index 45dc0306..c3777c87 100644
--- a/test/functionTests.js
+++ b/test/functionTests.js
@@ -25,6 +25,21 @@ describe('Function tests', function() {
     astErr.errors[0].message.should.be.exactly(msg);
   });
 
+  it('test reserved word for function name error', function() {
+    var astErr = parser.parseEval([
+      'function list() {}'
+    ].join('\n'), {
+      parser: {
+        suppressErrors: true
+      }
+    });
+
+    var msg = 'Parse Error : syntax error, unexpected \'list\' (T_LIST), expecting T_STRING on line 1';
+    astErr.errors.length.should.be.exactly(1);
+    astErr.errors[0].line.should.be.exactly(1);
+    astErr.errors[0].message.should.be.exactly(msg);
+  });
+
   it('test description', function () {
     // Get result from parser
     ast.children[0].kind.should.be.exactly('function');