From 4868b511f37a56fddc4f943cc3b0ecd3e65c7be1 Mon Sep 17 00:00:00 2001 From: Kapitan Oczywisty <44417092+KapitanOczywisty@users.noreply.github.com> Date: Sat, 14 Dec 2019 16:54:48 +0100 Subject: [PATCH] Typed properties, anonymous classes, object serialization --- grammars/php.cson | 52 +++++++++++++++++-- spec/php-spec.coffee | 120 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 169 insertions(+), 3 deletions(-) diff --git a/grammars/php.cson b/grammars/php.cson index 822a433f..1903f89b 100644 --- a/grammars/php.cson +++ b/grammars/php.cson @@ -201,7 +201,12 @@ ] } { - 'begin': '(?i)(?:^|(?<=}))\\s*(?:(abstract|final)\\s+)?(class)\\s+([a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*)' + 'begin': '''(?ix) + (?: + (?:^|(?<=}))\\s*(?:(abstract|final)\\s+)?(class)\\s+([a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*) + |\\b(new)\\s+(class)\\b # anonymous class + ) + ''' 'beginCaptures': '1': 'name': 'storage.modifier.${1:/downcase}.php' @@ -209,6 +214,10 @@ 'name': 'storage.type.class.php' '3': 'name': 'entity.name.type.class.php' + '4': + 'name': 'keyword.other.new.php' + '5': + 'name': 'storage.type.class.php' 'end': '}|(?=\\?>)' 'endCaptures': '0': @@ -462,7 +471,7 @@ (function)\\s+ (?i: (__(?:call|construct|debugInfo|destruct|get|set|isset|unset|toString| - clone|set_state|sleep|wakeup|autoload|invoke|callStatic)) + clone|set_state|sleep|wakeup|autoload|invoke|callStatic|serialize|unserialize)) |(?:(&)?\\s*([a-zA-Z_\\x{7f}-\\x{7fffffff}][a-zA-Z0-9_\\x{7f}-\\x{7fffffff}]*)) ) \\s*(\\() @@ -503,6 +512,43 @@ } ] } + { + 'match': '''(?xi) + ((?:(?:public|private|protected|static)(?:\\s+|(?=\\?)))+) # At least one modifier + (?:(\\?)?\\s* # Optional nullable + (\\\\?(?:[a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*\\\\)*) # Optional namespace + ([a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*))? # Typehinted class name + \\s+((\\$)[a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*) # Variable name + ''' + 'captures': + '1': + 'patterns': [ + { + 'match': 'public|private|protected|static' + 'name': 'storage.modifier.php' + } + ] + '2': + 'name': 'keyword.operator.nullable-type.php' + '3': + 'name': 'support.other.namespace.php' + 'patterns': [ + { + 'match': '(?i)[a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*' + 'name': 'storage.type.php' + } + { + 'match': '\\\\' + 'name': 'punctuation.separator.inheritance.php' + } + ] + '4': + 'name': 'storage.type.php' + '5': + 'name': 'variable.other.php' + '6': + 'name': 'punctuation.definition.variable.php' + } { 'include': '#invoke-call' } @@ -1889,7 +1935,7 @@ } ] 'instantiation': - 'begin': '(?i)(new)\\s+' + 'begin': '(?i)(new)\\s+(?!class\\b)' 'beginCaptures': '1': 'name': 'keyword.other.new.php' diff --git a/spec/php-spec.coffee b/spec/php-spec.coffee index d1f51048..87aa1473 100644 --- a/spec/php-spec.coffee +++ b/spec/php-spec.coffee @@ -544,6 +544,16 @@ describe 'PHP grammar', -> expect(tokens[6]).toEqual value: '/*', scopes: ['source.php', 'meta.class.php', 'meta.class.body.php', 'comment.block.php', 'punctuation.definition.comment.php'] expect(tokens[10]).toEqual value: '}', scopes: ['source.php', 'meta.class.php', 'punctuation.definition.class.end.bracket.curly.php'] + it 'tokenizes class instantiation', -> + {tokens} = grammar.tokenizeLine '$a = new ClassName();' + + expect(tokens[5]).toEqual value: 'new', scopes: ["source.php", "keyword.other.new.php"] + expect(tokens[6]).toEqual value: ' ', scopes: ["source.php"] + expect(tokens[7]).toEqual value: 'ClassName', scopes: ["source.php", "support.class.php"] + expect(tokens[8]).toEqual value: '(', scopes: ["source.php", "punctuation.definition.begin.bracket.round.php"] + expect(tokens[9]).toEqual value: ')', scopes: ["source.php", "punctuation.definition.end.bracket.round.php"] + expect(tokens[10]).toEqual value: ';', scopes: ["source.php", "punctuation.terminator.expression.php"] + it 'tokenizes class modifiers', -> {tokens} = grammar.tokenizeLine 'abstract class Test {}' @@ -568,6 +578,89 @@ describe 'PHP grammar', -> expect(tokens[8]).toEqual value: 'class', scopes: ['source.php', 'meta.class.php', 'storage.type.class.php'] expect(tokens[10]).toEqual value: 'Test2', scopes: ['source.php', 'meta.class.php', 'entity.name.type.class.php'] + describe 'properties', -> + it 'tokenizes types', -> + lines = grammar.tokenizeLines ''' + class A { + public int $a = 1; + } + ''' + + expect(lines[1][1]).toEqual value: 'public', scopes: ["source.php", "meta.class.php", "meta.class.body.php", "storage.modifier.php"] + expect(lines[1][3]).toEqual value: 'int', scopes: ["source.php", "meta.class.php", "meta.class.body.php", "storage.type.php"] + expect(lines[1][5]).toEqual value: '$', scopes: ["source.php", "meta.class.php", "meta.class.body.php", "variable.other.php", "punctuation.definition.variable.php"] + expect(lines[1][6]).toEqual value: 'a', scopes: ["source.php", "meta.class.php", "meta.class.body.php", "variable.other.php"] + + it 'tokenizes nullable types', -> + lines = grammar.tokenizeLines ''' + class A { + static ?string $b = 'Bee'; + } + ''' + + expect(lines[1][1]).toEqual value: 'static', scopes: ["source.php", "meta.class.php", "meta.class.body.php", "storage.modifier.php"] + expect(lines[1][3]).toEqual value: '?', scopes: ["source.php", "meta.class.php", "meta.class.body.php", "keyword.operator.nullable-type.php"] + expect(lines[1][4]).toEqual value: 'string', scopes: ["source.php", "meta.class.php", "meta.class.body.php", "storage.type.php"] + expect(lines[1][6]).toEqual value: '$', scopes: ["source.php", "meta.class.php", "meta.class.body.php", "variable.other.php", "punctuation.definition.variable.php"] + expect(lines[1][7]).toEqual value: 'b', scopes: ["source.php", "meta.class.php", "meta.class.body.php", "variable.other.php"] + + lines = grammar.tokenizeLines ''' + class A { + static? string $b; + } + ''' + + expect(lines[1][1]).toEqual value: 'static', scopes: ["source.php", "meta.class.php", "meta.class.body.php", "storage.modifier.php"] + expect(lines[1][2]).toEqual value: '?', scopes: ["source.php", "meta.class.php", "meta.class.body.php", "keyword.operator.nullable-type.php"] + expect(lines[1][4]).toEqual value: 'string', scopes: ["source.php", "meta.class.php", "meta.class.body.php", "storage.type.php"] + + it 'tokenizes namespaces', -> + lines = grammar.tokenizeLines ''' + class A { + public ?\\Space\\Test $c; + } + ''' + + expect(lines[1][1]).toEqual value: 'public', scopes: ["source.php", "meta.class.php", "meta.class.body.php", "storage.modifier.php"] + expect(lines[1][3]).toEqual value: '?', scopes: ["source.php", "meta.class.php", "meta.class.body.php", "keyword.operator.nullable-type.php"] + expect(lines[1][4]).toEqual value: '\\', scopes: ["source.php", "meta.class.php", "meta.class.body.php", "support.other.namespace.php", "punctuation.separator.inheritance.php"] + expect(lines[1][5]).toEqual value: 'Space', scopes: ["source.php", "meta.class.php", "meta.class.body.php", "support.other.namespace.php", "storage.type.php"] + expect(lines[1][6]).toEqual value: '\\', scopes: ["source.php", "meta.class.php", "meta.class.body.php", "support.other.namespace.php", "punctuation.separator.inheritance.php"] + expect(lines[1][7]).toEqual value: 'Test', scopes: ["source.php", "meta.class.php", "meta.class.body.php", "storage.type.php"] + expect(lines[1][9]).toEqual value: '$', scopes: ["source.php", "meta.class.php", "meta.class.body.php", "variable.other.php", "punctuation.definition.variable.php"] + expect(lines[1][10]).toEqual value: 'c', scopes: ["source.php", "meta.class.php", "meta.class.body.php", "variable.other.php"] + + it 'tokenizes multiple properties', -> + lines = grammar.tokenizeLines ''' + class A { + static int $a = 1; + public \\Other\\Type $b; + private static ? array $c1, $c2; + } + ''' + + expect(lines[1][1]).toEqual value: 'static', scopes: ["source.php", "meta.class.php", "meta.class.body.php", "storage.modifier.php"] + expect(lines[1][3]).toEqual value: 'int', scopes: ["source.php", "meta.class.php", "meta.class.body.php", "storage.type.php"] + expect(lines[1][5]).toEqual value: '$', scopes: ["source.php", "meta.class.php", "meta.class.body.php", "variable.other.php", "punctuation.definition.variable.php"] + expect(lines[1][6]).toEqual value: 'a', scopes: ["source.php", "meta.class.php", "meta.class.body.php", "variable.other.php"] + + expect(lines[2][1]).toEqual value: 'public', scopes: ["source.php", "meta.class.php", "meta.class.body.php", "storage.modifier.php"] + expect(lines[2][3]).toEqual value: '\\', scopes: ["source.php", "meta.class.php", "meta.class.body.php", "support.other.namespace.php", "punctuation.separator.inheritance.php"] + expect(lines[2][4]).toEqual value: 'Other', scopes: ["source.php", "meta.class.php", "meta.class.body.php", "support.other.namespace.php", "storage.type.php"] + expect(lines[2][5]).toEqual value: '\\', scopes: ["source.php", "meta.class.php", "meta.class.body.php", "support.other.namespace.php", "punctuation.separator.inheritance.php"] + expect(lines[2][6]).toEqual value: 'Type', scopes: ["source.php", "meta.class.php", "meta.class.body.php", "storage.type.php"] + expect(lines[2][8]).toEqual value: '$', scopes: ["source.php", "meta.class.php", "meta.class.body.php", "variable.other.php", "punctuation.definition.variable.php"] + expect(lines[2][9]).toEqual value: 'b', scopes: ["source.php", "meta.class.php", "meta.class.body.php", "variable.other.php"] + + expect(lines[3][1]).toEqual value: 'private', scopes: ["source.php", "meta.class.php", "meta.class.body.php", "storage.modifier.php"] + expect(lines[3][3]).toEqual value: 'static', scopes: ["source.php", "meta.class.php", "meta.class.body.php", "storage.modifier.php"] + expect(lines[3][5]).toEqual value: '?', scopes: ["source.php", "meta.class.php", "meta.class.body.php", "keyword.operator.nullable-type.php"] + expect(lines[3][7]).toEqual value: 'array', scopes: ["source.php", "meta.class.php", "meta.class.body.php", "storage.type.php"] + expect(lines[3][9]).toEqual value: '$', scopes: ["source.php", "meta.class.php", "meta.class.body.php", "variable.other.php", "punctuation.definition.variable.php"] + expect(lines[3][10]).toEqual value: 'c1', scopes: ["source.php", "meta.class.php", "meta.class.body.php", "variable.other.php"] + expect(lines[3][13]).toEqual value: '$', scopes: ["source.php", "meta.class.php", "meta.class.body.php", "variable.other.php", "punctuation.definition.variable.php"] + expect(lines[3][14]).toEqual value: 'c2', scopes: ["source.php", "meta.class.php", "meta.class.body.php", "variable.other.php"] + describe 'use statements', -> it 'tokenizes basic use statements', -> lines = grammar.tokenizeLines ''' @@ -695,6 +788,33 @@ describe 'PHP grammar', -> expect(lines[2][8]).toEqual value: ';', scopes: ['source.php', 'meta.class.php', 'meta.class.body.php', 'meta.use.php', 'punctuation.terminator.expression.php'] expect(lines[3][1]).toEqual value: '}', scopes: ['source.php', 'meta.class.php', 'meta.class.body.php', 'meta.use.php', 'punctuation.definition.use.end.bracket.curly.php'] + describe 'anonymous', -> + + it 'tokenizes anonymous class declarations', -> + {tokens} = grammar.tokenizeLine '$a = new class{ /* stuff */ };' + + expect(tokens[5]).toEqual value: 'new', scopes: ["source.php", "meta.class.php", "keyword.other.new.php"] + expect(tokens[6]).toEqual value: ' ', scopes: ["source.php", "meta.class.php"] + expect(tokens[7]).toEqual value: 'class', scopes: ["source.php", "meta.class.php", "storage.type.class.php"] + expect(tokens[8]).toEqual value: '{', scopes: ["source.php", "meta.class.php", "punctuation.definition.class.begin.bracket.curly.php"] + expect(tokens[9]).toEqual value: ' ', scopes: ["source.php", "meta.class.php", "meta.class.body.php"] + expect(tokens[10]).toEqual value: '/*', scopes: ["source.php", "meta.class.php", "meta.class.body.php", "comment.block.php", "punctuation.definition.comment.php"] + expect(tokens[11]).toEqual value: ' stuff ', scopes: ["source.php", "meta.class.php", "meta.class.body.php", "comment.block.php"] + expect(tokens[12]).toEqual value: '*/', scopes: ["source.php", "meta.class.php", "meta.class.body.php", "comment.block.php", "punctuation.definition.comment.php"] + expect(tokens[13]).toEqual value: ' ', scopes: ["source.php", "meta.class.php", "meta.class.body.php"] + expect(tokens[14]).toEqual value: '}', scopes: ["source.php", "meta.class.php", "punctuation.definition.class.end.bracket.curly.php"] + expect(tokens[15]).toEqual value: ';', scopes: ["source.php", "punctuation.terminator.expression.php"] + + it 'tokenizes inheritance correctly', -> + {tokens} = grammar.tokenizeLine '$a = new class extends Test implements ITest { /* stuff */ };' + + expect(tokens[5]).toEqual value: 'new', scopes: ["source.php", "meta.class.php", "keyword.other.new.php"] + expect(tokens[7]).toEqual value: 'class', scopes: ["source.php", "meta.class.php", "storage.type.class.php"] + expect(tokens[9]).toEqual value: 'extends', scopes: ["source.php", "meta.class.php", "storage.modifier.extends.php"] + expect(tokens[11]).toEqual value: 'Test', scopes: ["source.php", "meta.class.php", "meta.other.inherited-class.php", "entity.other.inherited-class.php"] + expect(tokens[13]).toEqual value: 'implements', scopes: ["source.php", "meta.class.php", "storage.modifier.implements.php"] + expect(tokens[15]).toEqual value: 'ITest', scopes: ["source.php", "meta.class.php", "meta.other.inherited-class.php", "entity.other.inherited-class.php"] + describe 'functions', -> it 'tokenizes functions with no arguments', -> {tokens} = grammar.tokenizeLine 'function test() {}'