From 427acd916a0fb43b69a70aa2effaf027b092f4a9 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 14 Dec 2017 10:11:09 -0800 Subject: [PATCH 01/21] Start work on tree-sitter PHP grammar --- grammars/tree-sitter-php.cson | 39 +++++++++++++++++++++++++++++++++++ package.json | 3 +++ 2 files changed, 42 insertions(+) create mode 100644 grammars/tree-sitter-php.cson diff --git a/grammars/tree-sitter-php.cson b/grammars/tree-sitter-php.cson new file mode 100644 index 00000000..d3518861 --- /dev/null +++ b/grammars/tree-sitter-php.cson @@ -0,0 +1,39 @@ +id: 'php' +name: 'PHP' +type: 'tree-sitter' +parser: 'tree-sitter-php' + +comments: + start: '#' + +fileTypes: [ + 'php' + 'phpt' +] + +folds: [ + { + start: {type: '{', index: 0}, + end: {'}', index: -1} + } + { + start: {type: '(', index: 0}, + end: {')', index: -1} + } +] + +scopes: + '"echo"': 'support.function' + 'string': 'string.quoted' + 'float': 'constant.numeric' + + 'function_call_expression > qualified_name': 'entity.name.function' + 'variable_name': 'variable' + + '"if"': 'keyword.control' + '"else"': 'keyword.control' + '"elseif"': 'keyword.control' + '"endif"': 'keyword.control' + + '"for"': 'keyword.control' + '"while"': 'keyword.control' diff --git a/package.json b/package.json index d2b5b746..0f82caac 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,9 @@ "bugs": { "url": "https://github.com/atom/language-php/issues" }, + "dependencies": { + "tree-sitter-php": "^0.1.1" + }, "devDependencies": { "coffeelint": "^1.10.1" } From 1ec9137a35bbbd8c6de1356c19e84ee7ad7cf713 Mon Sep 17 00:00:00 2001 From: joshvera Date: Thu, 14 Dec 2017 16:52:50 -0800 Subject: [PATCH 02/21] Stub in more syntax --- grammars/tree-sitter-php.cson | 67 +++++++++++++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 2 deletions(-) diff --git a/grammars/tree-sitter-php.cson b/grammars/tree-sitter-php.cson index d3518861..af085d7b 100644 --- a/grammars/tree-sitter-php.cson +++ b/grammars/tree-sitter-php.cson @@ -30,10 +30,73 @@ scopes: 'function_call_expression > qualified_name': 'entity.name.function' 'variable_name': 'variable' + 'program': 'source.php' + 'ERROR': 'syntax-error' + + + 'class_declaration > name': 'support.storage.type' + 'class_base_clause > qualified_name -> name': 'storage.modifier.extends.php' + + 'function_definition > name': 'entity.name.function' + 'function_call_expression > name': 'entity.name.function' + + 'method_declaration > name': 'entity.name.function' + 'method_declaration > function_definition > name': 'entity.name.function' + 'constructor_definition > "__construct"': 'entity.name.function' + + 'object_creation_expression > qualified_name > name': 'meta.class.instance.constructor' + 'object_creation_expression > new_variable > simple_variable': 'meta.class.instance.constructor' + + 'integer': 'constant.numeric.decimal' + 'float': 'constant.numeric.decimal' + 'string': 'string.quoted.single' + 'regex': 'string.regexp' + 'true': 'constant.language.boolean.true' + 'false': 'constant.language.boolean.false' + 'comment': 'comment.block' + + '"("': 'punctuation.definition.parameters.begin.bracket.round' + '")"': 'punctuation.definition.parameters.end.bracket.round' + '"{"': 'punctuation.definition.function.body.begin.bracket.curly' + '"}"': 'punctuation.definition.function.body.end.bracket.curly' + + '"static"': 'storage.modifier' + '"public"': 'storage.modifier' + '"private"': 'storage.modifier' + '"protected"': 'storage.modifier' + '"class"': 'storage.type.class' + '"function"': 'storage.type.function' + '"type"': 'storage.type.type' + + '"+"': 'keyword.operator' + '"-"': 'keyword.operator' + '"*"': 'keyword.operator' + '"/"': 'keyword.operator' + + '"."': 'meta.delimiter.property.period' + '","': 'meta.delimiter.object.comma' + '"if"': 'keyword.control' + '"do"': 'keyword.control' '"else"': 'keyword.control' '"elseif"': 'keyword.control' '"endif"': 'keyword.control' - - '"for"': 'keyword.control' '"while"': 'keyword.control' + '"endwhile"': 'keyword.control' + '"for"': 'keyword.control' + '"endfor"': 'keyword.control' + '"foreach"': 'keyword.control' + '"endforeach"': 'keyword.control' + '"declare"': 'keyword.control' + '"enddeclare"': 'keyword.control' + '"return"': 'keyword.control' + '"break"': 'keyword.control' + '"continue"': 'keyword.control' + '"throw"': 'keyword.control' + '"try"': 'keyword.control' + '"catch"': 'keyword.control' + '"finally"': 'keyword.control' + '"switch"': 'keyword.control' + '"endswitch"': 'keyword.control' + '"case"': 'keyword.control' + '"default"': 'keyword.control' From 21a728a63139ea65fbc3a67b41126e7b6ca5f980 Mon Sep 17 00:00:00 2001 From: joshvera Date: Fri, 15 Dec 2017 15:38:39 -0800 Subject: [PATCH 03/21] Fix css syntax --- grammars/tree-sitter-php.cson | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grammars/tree-sitter-php.cson b/grammars/tree-sitter-php.cson index af085d7b..7d858c5a 100644 --- a/grammars/tree-sitter-php.cson +++ b/grammars/tree-sitter-php.cson @@ -35,7 +35,7 @@ scopes: 'class_declaration > name': 'support.storage.type' - 'class_base_clause > qualified_name -> name': 'storage.modifier.extends.php' + 'class_base_clause > qualified_name > name': 'storage.modifier.extends.php' 'function_definition > name': 'entity.name.function' 'function_call_expression > name': 'entity.name.function' From 6cdaa1a553866abced1d2b38a4d99f12605add85 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 15 Dec 2017 16:29:37 -0800 Subject: [PATCH 04/21] Add legacy scope name property to tree-sitter grammar --- grammars/tree-sitter-php.cson | 1 + 1 file changed, 1 insertion(+) diff --git a/grammars/tree-sitter-php.cson b/grammars/tree-sitter-php.cson index 7d858c5a..58171fd7 100644 --- a/grammars/tree-sitter-php.cson +++ b/grammars/tree-sitter-php.cson @@ -2,6 +2,7 @@ id: 'php' name: 'PHP' type: 'tree-sitter' parser: 'tree-sitter-php' +legacyScopeName: 'source.php' comments: start: '#' From 2e4caf5cdfddc2024e4f1571e0ae391aae120694 Mon Sep 17 00:00:00 2001 From: Linus Eriksson Date: Sat, 15 Sep 2018 01:53:16 +0200 Subject: [PATCH 05/21] :arrow_up: tree-sitter-php --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0f82caac..bb425853 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "url": "https://github.com/atom/language-php/issues" }, "dependencies": { - "tree-sitter-php": "^0.1.1" + "tree-sitter-php": "^0.13.0" }, "devDependencies": { "coffeelint": "^1.10.1" From 4867b6e72c460e7322764a6e81aa0cd6651f60a7 Mon Sep 17 00:00:00 2001 From: Linus Eriksson Date: Sat, 15 Sep 2018 01:55:37 +0200 Subject: [PATCH 06/21] Update to latest API changes --- grammars/tree-sitter-php.cson | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/grammars/tree-sitter-php.cson b/grammars/tree-sitter-php.cson index 58171fd7..f2475e38 100644 --- a/grammars/tree-sitter-php.cson +++ b/grammars/tree-sitter-php.cson @@ -1,8 +1,7 @@ -id: 'php' name: 'PHP' +scopeName: 'source.php' type: 'tree-sitter' parser: 'tree-sitter-php' -legacyScopeName: 'source.php' comments: start: '#' From 07cd3a7638f3dfd2813626cdffe6db8190bde3fb Mon Sep 17 00:00:00 2001 From: Linus Eriksson Date: Sat, 15 Sep 2018 01:55:57 +0200 Subject: [PATCH 07/21] Do we commit package-lock??? --- package-lock.json | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/package-lock.json b/package-lock.json index 4a6caf65..9afb4225 100644 --- a/package-lock.json +++ b/package-lock.json @@ -103,6 +103,11 @@ "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", "dev": true }, + "nan": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.0.tgz", + "integrity": "sha512-F4miItu2rGnV2ySkXOQoA8FKz/SR2Q2sWP0sbTxNxz/tuokeC8WxOhPMcwi0qIyGtVn/rrSeLbvVkznqCdwYnw==" + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -140,6 +145,14 @@ "integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=", "dev": true }, + "tree-sitter-php": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/tree-sitter-php/-/tree-sitter-php-0.13.0.tgz", + "integrity": "sha512-PlYvZJ+bwMVaUvbmDvaVBzsZqhDmx4mILG2Yvz6vhWMThHkq06NWeuV9YX0H7TrYzVBJQ0mF1My4gSPCE/0D0A==", + "requires": { + "nan": "^2.10.0" + } + }, "wordwrap": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", From a1d6a89182119844239cc7f5c55859e49070148a Mon Sep 17 00:00:00 2001 From: Linus Eriksson Date: Sat, 15 Sep 2018 02:04:51 +0200 Subject: [PATCH 08/21] The scopeName for php is text.html.php --- grammars/tree-sitter-php.cson | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grammars/tree-sitter-php.cson b/grammars/tree-sitter-php.cson index f2475e38..6fb11aba 100644 --- a/grammars/tree-sitter-php.cson +++ b/grammars/tree-sitter-php.cson @@ -1,5 +1,5 @@ name: 'PHP' -scopeName: 'source.php' +scopeName: 'text.html.php' type: 'tree-sitter' parser: 'tree-sitter-php' From 3d71b8042728db656f488232a2417623e42bc4fc Mon Sep 17 00:00:00 2001 From: Linus Eriksson Date: Mon, 15 Oct 2018 22:39:08 +0200 Subject: [PATCH 09/21] Add more syntax --- grammars/tree-sitter-php.cson | 67 +++++++++++++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 2 deletions(-) diff --git a/grammars/tree-sitter-php.cson b/grammars/tree-sitter-php.cson index 6fb11aba..63f845e7 100644 --- a/grammars/tree-sitter-php.cson +++ b/grammars/tree-sitter-php.cson @@ -24,6 +24,14 @@ folds: [ scopes: '"echo"': 'support.function' + '"print"': 'support.function' + '"unset"': 'support.function' + '"isset"': 'support.function' + '"list"': 'support.function' + '"eval"': 'support.function' + '"array"': 'support.function' + '"list"': 'support.function' + '"empty"': 'support.function' 'string': 'string.quoted' 'float': 'constant.numeric' @@ -33,9 +41,11 @@ scopes: 'program': 'source.php' 'ERROR': 'syntax-error' + 'class_declaration > name': 'entity.name.type.class' + 'class_base_clause > qualified_name > name': 'entity.other.inherited-class' - 'class_declaration > name': 'support.storage.type' - 'class_base_clause > qualified_name > name': 'storage.modifier.extends.php' + 'interface_declaration > name': 'entity.name.type.interface' + 'class_interface_clause > qualified_name > name': 'entity.other.implemented-interface' 'function_definition > name': 'entity.name.function' 'function_call_expression > name': 'entity.name.function' @@ -64,14 +74,63 @@ scopes: '"public"': 'storage.modifier' '"private"': 'storage.modifier' '"protected"': 'storage.modifier' + '"global"': 'storage.modifier' + '"const"': 'storage.modifier' + '"abstract"': 'storage.modifier.abstract' + '"extends"': 'storage.modifier.extends' + '"implements"': 'storage.modifier.implements' + '"final"': 'storage.modifier.final' + '"use"': 'keyword.other.use' + '"namespace"': 'keyword.other.namespace' + '"callable"': 'storage.type' + '"var"': 'storage.type' + '"trait"': 'storage.type.trait' '"class"': 'storage.type.class' + '"interface"': 'storage.type.interface' '"function"': 'storage.type.function' '"type"': 'storage.type.type' + 'cast_expression > cast_type': 'storage.type.cast' '"+"': 'keyword.operator' '"-"': 'keyword.operator' '"*"': 'keyword.operator' '"/"': 'keyword.operator' + '"%"': 'keyword.operator' + '"**"': 'keyword.operator' + '"=="': 'keyword.operator' + '"==="': 'keyword.operator' + '"!="': 'keyword.operator' + '"!="': 'keyword.operator' + '"!=="': 'keyword.operator' + '"<"': 'keyword.operator' + '">"': 'keyword.operator' + '"<>"': 'keyword.operator' + '"<="': 'keyword.operator' + '">="': 'keyword.operator' + '"<=>"': 'keyword.operator' + '"&"': 'keyword.operator' + '"|"': 'keyword.operator' + '"^"': 'keyword.operator' + '"~"': 'keyword.opeator' + '"<<"': 'keyword.operator' + '">>"': 'keyword.operator' + '"++"': 'keyword.operator' + '"--"': 'keyword.operator' + '"and"': 'keyword.operator' + '"or"': 'keyword.operator' + '"xor"': 'keyword.operator' + '"!"': 'keyword.operator' + '"&&"': 'keyword.operator' + '"||"': 'keyword.operator' + '"."': 'keyword.operator' + '"?"': 'keyword.operator' + '":"': 'keyword.operator' + '"??"': 'keyword.operator' + '"as"': 'keyword.operator.logical' + '"new"': 'keyword.other.new' + '"clone"': 'keyword.other.clone' + '"insteadof"': 'keyword.other.insteadof' + '"instanceof"': 'keyword.operator.type' '"."': 'meta.delimiter.property.period' '","': 'meta.delimiter.object.comma' @@ -100,3 +159,7 @@ scopes: '"endswitch"': 'keyword.control' '"case"': 'keyword.control' '"default"': 'keyword.control' + '"yield"': 'keyword.control.yield' + '"goto"': 'keyword.control.goto' + '"exit"': 'keyword.control.exit' + '"die"': 'keyword.control.die' From 180a5c20120c22152e1dea6cdcbe5b16a87e4083 Mon Sep 17 00:00:00 2001 From: Linus Eriksson Date: Tue, 16 Oct 2018 22:45:11 +0200 Subject: [PATCH 10/21] WIP --- grammars/tree-sitter-php.cson | 104 ++++++++++++++++++++-------------- 1 file changed, 62 insertions(+), 42 deletions(-) diff --git a/grammars/tree-sitter-php.cson b/grammars/tree-sitter-php.cson index 63f845e7..a1c51c44 100644 --- a/grammars/tree-sitter-php.cson +++ b/grammars/tree-sitter-php.cson @@ -3,6 +3,10 @@ scopeName: 'text.html.php' type: 'tree-sitter' parser: 'tree-sitter-php' +firstLineRegex: [ + '^\\s*<\\?([pP][hH][pP]|=|\\s|$)' +] + comments: start: '#' @@ -12,14 +16,26 @@ fileTypes: [ ] folds: [ + { + type: 'comment' + } { start: {type: '{', index: 0}, end: {'}', index: -1} } + { + type: ['class_declaration', 'interface_declaration'] + start: {type: '{'}, + end: {type: '}'} + } { start: {type: '(', index: 0}, end: {')', index: -1} } + { + start: {type: '[', index: 0}, + end: {']', index: -1} + } ] scopes: @@ -37,6 +53,7 @@ scopes: 'function_call_expression > qualified_name': 'entity.name.function' 'variable_name': 'variable' + 'member_access_expression > name': 'variable.other.object.property' 'program': 'source.php' 'ERROR': 'syntax-error' @@ -49,6 +66,7 @@ scopes: 'function_definition > name': 'entity.name.function' 'function_call_expression > name': 'entity.name.function' + '"require_once", "require", "include_once", "include"': 'entity.name.function' 'method_declaration > name': 'entity.name.function' 'method_declaration > function_definition > name': 'entity.name.function' @@ -91,48 +109,50 @@ scopes: '"type"': 'storage.type.type' 'cast_expression > cast_type': 'storage.type.cast' - '"+"': 'keyword.operator' - '"-"': 'keyword.operator' - '"*"': 'keyword.operator' - '"/"': 'keyword.operator' - '"%"': 'keyword.operator' - '"**"': 'keyword.operator' - '"=="': 'keyword.operator' - '"==="': 'keyword.operator' - '"!="': 'keyword.operator' - '"!="': 'keyword.operator' - '"!=="': 'keyword.operator' - '"<"': 'keyword.operator' - '">"': 'keyword.operator' - '"<>"': 'keyword.operator' - '"<="': 'keyword.operator' - '">="': 'keyword.operator' - '"<=>"': 'keyword.operator' - '"&"': 'keyword.operator' - '"|"': 'keyword.operator' - '"^"': 'keyword.operator' - '"~"': 'keyword.opeator' - '"<<"': 'keyword.operator' - '">>"': 'keyword.operator' - '"++"': 'keyword.operator' - '"--"': 'keyword.operator' - '"and"': 'keyword.operator' - '"or"': 'keyword.operator' - '"xor"': 'keyword.operator' - '"!"': 'keyword.operator' - '"&&"': 'keyword.operator' - '"||"': 'keyword.operator' - '"."': 'keyword.operator' - '"?"': 'keyword.operator' - '":"': 'keyword.operator' - '"??"': 'keyword.operator' - '"as"': 'keyword.operator.logical' - '"new"': 'keyword.other.new' - '"clone"': 'keyword.other.clone' - '"insteadof"': 'keyword.other.insteadof' - '"instanceof"': 'keyword.operator.type' - - '"."': 'meta.delimiter.property.period' + '"+"': 'keyword.operator.php' + '"-"': 'keyword.operator.php' + '"*"': 'keyword.operator.php' + '"/"': 'keyword.operator.php' + '"%"': 'keyword.operator.php' + '"**"': 'keyword.operator.php' + '"="': 'keyword.operator.php' + '"=="': 'keyword.operator.php' + '"==="': 'keyword.operator.php' + '"!="': 'keyword.operator.php' + '"!="': 'keyword.operator.php' + '"!=="': 'keyword.operator.php' + '"<"': 'keyword.operator.php' + '">"': 'keyword.operator.php' + '"<>"': 'keyword.operator.php' + '"<="': 'keyword.operator.php' + '"=>"': 'keyword.operator.php' + '">="': 'keyword.operator.php' + '"<=>"': 'keyword.operator.php' + '"&"': 'keyword.operator.php' + '"|"': 'keyword.operator.php' + '"^"': 'keyword.operator.php' + '"~"': 'keyword.opeator.php' + '"<<"': 'keyword.operator.php' + '">>"': 'keyword.operator.php' + '"++"': 'keyword.operator.php' + '"--"': 'keyword.operator.php' + '"and"': 'keyword.operator.php' + '"or"': 'keyword.operator.php' + '"xor"': 'keyword.operator.php' + '"!"': 'keyword.operator.php' + '"&&"': 'keyword.operator.php' + '"||"': 'keyword.operator.php' + '"."': 'keyword.operator.php' + '"?"': 'keyword.operator.php' + '":"': 'keyword.operator.php' + '"??"': 'keyword.operator.php' + '"as"': 'keyword.logical' + '"new"': 'keyword.new' + '"clone"': 'keyword.clone' + '"insteadof"': 'keyword.insteadof' + '"instanceof"': 'keyword.type' + + '"->"': 'meta.delimiter.property.arrow' '","': 'meta.delimiter.object.comma' '"if"': 'keyword.control' From 77aab6a4a8725fc58cf4e9c23b23581c8c5867fd Mon Sep 17 00:00:00 2001 From: Linus Eriksson Date: Sun, 28 Oct 2018 22:33:11 +0100 Subject: [PATCH 11/21] scope scoped call expressions --- grammars/tree-sitter-php.cson | 2 ++ 1 file changed, 2 insertions(+) diff --git a/grammars/tree-sitter-php.cson b/grammars/tree-sitter-php.cson index a1c51c44..13a8773b 100644 --- a/grammars/tree-sitter-php.cson +++ b/grammars/tree-sitter-php.cson @@ -52,6 +52,8 @@ scopes: 'float': 'constant.numeric' 'function_call_expression > qualified_name': 'entity.name.function' + 'scoped_call_expression > name': 'entity.name.function' + 'variable_name': 'variable' 'member_access_expression > name': 'variable.other.object.property' From 3f65dec3b23188b7969b0dc00a939faafbcdadb2 Mon Sep 17 00:00:00 2001 From: Linus Eriksson Date: Sun, 28 Oct 2018 22:42:21 +0100 Subject: [PATCH 12/21] Scope member call expressions and constants Also remove scope for variable and for syntax-error --- grammars/tree-sitter-php.cson | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/grammars/tree-sitter-php.cson b/grammars/tree-sitter-php.cson index 13a8773b..b3b8db2c 100644 --- a/grammars/tree-sitter-php.cson +++ b/grammars/tree-sitter-php.cson @@ -51,14 +51,9 @@ scopes: 'string': 'string.quoted' 'float': 'constant.numeric' - 'function_call_expression > qualified_name': 'entity.name.function' - 'scoped_call_expression > name': 'entity.name.function' - - 'variable_name': 'variable' 'member_access_expression > name': 'variable.other.object.property' 'program': 'source.php' - 'ERROR': 'syntax-error' 'class_declaration > name': 'entity.name.type.class' 'class_base_clause > qualified_name > name': 'entity.other.inherited-class' @@ -68,6 +63,9 @@ scopes: 'function_definition > name': 'entity.name.function' 'function_call_expression > name': 'entity.name.function' + 'function_call_expression > qualified_name': 'entity.name.function' + 'scoped_call_expression > name': 'entity.name.function' + 'member_call_expression > name': 'entity.name.function' '"require_once", "require", "include_once", "include"': 'entity.name.function' 'method_declaration > name': 'entity.name.function' @@ -77,6 +75,8 @@ scopes: 'object_creation_expression > qualified_name > name': 'meta.class.instance.constructor' 'object_creation_expression > new_variable > simple_variable': 'meta.class.instance.constructor' + 'const_element > name': 'constant' + 'class_constant_access_expression >name': 'constant' 'integer': 'constant.numeric.decimal' 'float': 'constant.numeric.decimal' 'string': 'string.quoted.single' From dec32335e1b8af4d1748e177877f2e609f24bc52 Mon Sep 17 00:00:00 2001 From: Linus Eriksson Date: Sun, 28 Oct 2018 22:53:08 +0100 Subject: [PATCH 13/21] Turn off useTreeSitterParsers in the textmate specs --- spec/html-spec.coffee | 1 + spec/php-spec.coffee | 1 + 2 files changed, 2 insertions(+) diff --git a/spec/html-spec.coffee b/spec/html-spec.coffee index 814de60a..7812be27 100644 --- a/spec/html-spec.coffee +++ b/spec/html-spec.coffee @@ -2,6 +2,7 @@ describe 'PHP in HTML', -> grammar = null beforeEach -> + atom.config.set('core.useTreeSitterParsers', false) waitsForPromise -> atom.packages.activatePackage 'language-php' diff --git a/spec/php-spec.coffee b/spec/php-spec.coffee index 73e4c8e7..fe9c5089 100644 --- a/spec/php-spec.coffee +++ b/spec/php-spec.coffee @@ -2,6 +2,7 @@ describe 'PHP grammar', -> grammar = null beforeEach -> + atom.config.set('core.useTreeSitterParsers', false) waitsForPromise -> atom.packages.activatePackage 'language-php' From 8c8009e3953c7e396b1c4873d49c110acdbe4f35 Mon Sep 17 00:00:00 2001 From: Linus Eriksson Date: Fri, 30 Nov 2018 22:03:58 +0100 Subject: [PATCH 14/21] :arrow_up: tree-sitter-php@0.13.1 --- package-lock.json | 12 ++++++------ package.json | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9afb4225..7cf66369 100644 --- a/package-lock.json +++ b/package-lock.json @@ -104,9 +104,9 @@ "dev": true }, "nan": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.0.tgz", - "integrity": "sha512-F4miItu2rGnV2ySkXOQoA8FKz/SR2Q2sWP0sbTxNxz/tuokeC8WxOhPMcwi0qIyGtVn/rrSeLbvVkznqCdwYnw==" + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.1.tgz", + "integrity": "sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA==" }, "once": { "version": "1.4.0", @@ -146,9 +146,9 @@ "dev": true }, "tree-sitter-php": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/tree-sitter-php/-/tree-sitter-php-0.13.0.tgz", - "integrity": "sha512-PlYvZJ+bwMVaUvbmDvaVBzsZqhDmx4mILG2Yvz6vhWMThHkq06NWeuV9YX0H7TrYzVBJQ0mF1My4gSPCE/0D0A==", + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/tree-sitter-php/-/tree-sitter-php-0.13.1.tgz", + "integrity": "sha512-Qc7w4D0XG3KGcfcEM9K/YBighVw2uijTIENmqPp32HcCR/8IZwzuNSbAkbD44PPw1WQFvtBZVJqOc91kMH+LPA==", "requires": { "nan": "^2.10.0" } diff --git a/package.json b/package.json index bb425853..06702b58 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "url": "https://github.com/atom/language-php/issues" }, "dependencies": { - "tree-sitter-php": "^0.13.0" + "tree-sitter-php": "^0.13.1" }, "devDependencies": { "coffeelint": "^1.10.1" From 4ad05a800b6cdff37fb54260bc8856e4b04a61bf Mon Sep 17 00:00:00 2001 From: Linus Eriksson Date: Fri, 30 Nov 2018 22:04:15 +0100 Subject: [PATCH 15/21] Fold formal_parameters --- grammars/tree-sitter-php.cson | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/grammars/tree-sitter-php.cson b/grammars/tree-sitter-php.cson index b3b8db2c..930f6e8e 100644 --- a/grammars/tree-sitter-php.cson +++ b/grammars/tree-sitter-php.cson @@ -28,6 +28,11 @@ folds: [ start: {type: '{'}, end: {type: '}'} } + { + type: 'formal_parameters' + start: {type: '('} + end: {type: ')'} + } { start: {type: '(', index: 0}, end: {')', index: -1} From e85ab964217e24c9383baac0eae8bce06998e839 Mon Sep 17 00:00:00 2001 From: Linus Eriksson Date: Fri, 30 Nov 2018 22:04:22 +0100 Subject: [PATCH 16/21] :art: --- grammars/tree-sitter-php.cson | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/grammars/tree-sitter-php.cson b/grammars/tree-sitter-php.cson index 930f6e8e..8409f4f0 100644 --- a/grammars/tree-sitter-php.cson +++ b/grammars/tree-sitter-php.cson @@ -44,6 +44,8 @@ folds: [ ] scopes: + 'program': 'source.php' + '"echo"': 'support.function' '"print"': 'support.function' '"unset"': 'support.function' @@ -58,8 +60,6 @@ scopes: 'member_access_expression > name': 'variable.other.object.property' - 'program': 'source.php' - 'class_declaration > name': 'entity.name.type.class' 'class_base_clause > qualified_name > name': 'entity.other.inherited-class' @@ -107,6 +107,7 @@ scopes: '"final"': 'storage.modifier.final' '"use"': 'keyword.other.use' '"namespace"': 'keyword.other.namespace' + '"callable"': 'storage.type' '"var"': 'storage.type' '"trait"': 'storage.type.trait' From accafeac89af84c712fedff0bcc12f2cfb6a380f Mon Sep 17 00:00:00 2001 From: Linus Eriksson Date: Tue, 30 Oct 2018 15:51:25 +0100 Subject: [PATCH 17/21] Add more scopes Fixes scopes of anonymous nodes Scopes constant.language.php for language constants Scope the class of catch expressions as a class name Change object_creation_expression to scope as entity.name.type.class --- grammars/tree-sitter-php.cson | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/grammars/tree-sitter-php.cson b/grammars/tree-sitter-php.cson index 8409f4f0..48810893 100644 --- a/grammars/tree-sitter-php.cson +++ b/grammars/tree-sitter-php.cson @@ -61,8 +61,11 @@ scopes: 'member_access_expression > name': 'variable.other.object.property' 'class_declaration > name': 'entity.name.type.class' + 'catch_clause > qualified_name > name': 'entity.name.type.class' 'class_base_clause > qualified_name > name': 'entity.other.inherited-class' + 'qualified_name > name': {match: /^(TRUE|FALSE|NULL|__(FILE|DIR|FUNCTION|CLASS|METHOD|LINE|NAMESPACE)__|ON|OFF|YES|NO|NL|BR|TAB)$/i, scopes: 'constant.language.php'} + 'interface_declaration > name': 'entity.name.type.interface' 'class_interface_clause > qualified_name > name': 'entity.other.implemented-interface' @@ -77,7 +80,7 @@ scopes: 'method_declaration > function_definition > name': 'entity.name.function' 'constructor_definition > "__construct"': 'entity.name.function' - 'object_creation_expression > qualified_name > name': 'meta.class.instance.constructor' + 'object_creation_expression > qualified_name > name': 'entity.name.type.class' 'object_creation_expression > new_variable > simple_variable': 'meta.class.instance.constructor' 'const_element > name': 'constant' @@ -90,10 +93,14 @@ scopes: 'false': 'constant.language.boolean.false' 'comment': 'comment.block' - '"("': 'punctuation.definition.parameters.begin.bracket.round' - '")"': 'punctuation.definition.parameters.end.bracket.round' - '"{"': 'punctuation.definition.function.body.begin.bracket.curly' - '"}"': 'punctuation.definition.function.body.end.bracket.curly' + 'variable_name > "$"': 'punctuation.definition.variable.php' + '"("': 'punctuation.definition.parameters.begin.bracket.round.php' + '")"': 'punctuation.definition.parameters.end.bracket.round.php' + '"{"': 'punctuation.definition.begin.bracket.curly.php' + '"}"': 'punctuation.definition.end.bracket.curly.php' + '"["': 'punctuation.section.array.begin.php' + '"]"': 'punctuation.section.array.end.php' + '";"': 'punctuation.terminator.expression.php' '"static"': 'storage.modifier' '"public"': 'storage.modifier' @@ -160,8 +167,8 @@ scopes: '"insteadof"': 'keyword.insteadof' '"instanceof"': 'keyword.type' - '"->"': 'meta.delimiter.property.arrow' - '","': 'meta.delimiter.object.comma' + '"->"': 'keyword.operator.class' + '","': 'punctuation.separator.delimiter.php' '"if"': 'keyword.control' '"do"': 'keyword.control' From ae4ab1eea1b022434d45980acfc0ba34d322ee23 Mon Sep 17 00:00:00 2001 From: Clayton Carter Date: Wed, 27 Apr 2022 20:35:23 -0400 Subject: [PATCH 18/21] Update tree-sitter-php This uses a custom, "off label" package that is just the up-to-date tree-sitter-php rebuilt to use an older tree-sitter ABI that is compatible w/ the older verion that ships w/ Atom. --- package-lock.json | 16 ++++++++-------- package.json | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7cf66369..bb8f3014 100644 --- a/package-lock.json +++ b/package-lock.json @@ -104,9 +104,9 @@ "dev": true }, "nan": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.1.tgz", - "integrity": "sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA==" + "version": "2.14.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", + "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==" }, "once": { "version": "1.4.0", @@ -145,12 +145,12 @@ "integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=", "dev": true }, - "tree-sitter-php": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/tree-sitter-php/-/tree-sitter-php-0.13.1.tgz", - "integrity": "sha512-Qc7w4D0XG3KGcfcEM9K/YBighVw2uijTIENmqPp32HcCR/8IZwzuNSbAkbD44PPw1WQFvtBZVJqOc91kMH+LPA==", + "tree-sitter-php-abc": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/tree-sitter-php-abc/-/tree-sitter-php-abc-0.17.0.tgz", + "integrity": "sha512-zpKnPkhqBV2mw0sP+BG+OWFBh9/vj6Y4OALWRn/iQuOqCVHbLgW/Q0x4oTCySuNFqYg4zuMgCyWgr5i8Q7uMew==", "requires": { - "nan": "^2.10.0" + "nan": "^2.14.0" } }, "wordwrap": { diff --git a/package.json b/package.json index 06702b58..8cd638da 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "url": "https://github.com/atom/language-php/issues" }, "dependencies": { - "tree-sitter-php": "^0.13.1" + "tree-sitter-php-abc": "^0.17.0", }, "devDependencies": { "coffeelint": "^1.10.1" From 433adae66a16dbdb6921dd00e2b84552e6b94a97 Mon Sep 17 00:00:00 2001 From: Clayton Carter Date: Wed, 27 Apr 2022 20:38:20 -0400 Subject: [PATCH 19/21] Add tests for tree-sitter grammar These are exhaustive to tests ported from the TextMate grammar test for maximum compatibility. Never-the-less, there are still lots of FIXME comments to flag area that still need improvement or work. --- package-lock.json | 6 + package.json | 3 +- spec/tree-sitter-spec.js | 4296 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 4304 insertions(+), 1 deletion(-) create mode 100644 spec/tree-sitter-spec.js diff --git a/package-lock.json b/package-lock.json index bb8f3014..58a9e112 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,6 +46,12 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, + "dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", + "dev": true + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", diff --git a/package.json b/package.json index 8cd638da..f8e6bc5d 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "tree-sitter-php-abc": "^0.17.0", }, "devDependencies": { - "coffeelint": "^1.10.1" + "coffeelint": "^1.10.1", + "dedent": "^0.7.0" } } diff --git a/spec/tree-sitter-spec.js b/spec/tree-sitter-spec.js new file mode 100644 index 00000000..fc2eb906 --- /dev/null +++ b/spec/tree-sitter-spec.js @@ -0,0 +1,4296 @@ +const dedent = require("dedent"); +const {expectTokensToEqual, toHaveScopesAtPosition, setPhpText, nextHighlightingUpdate} = require('./tree-sitter-helpers') + +describe("Tree-sitter PHP grammar", () => { + var editor; + + beforeEach(async () => { + atom.config.set("core.useTreeSitterParsers", true); + await atom.packages.activatePackage("language-php"); + editor = await atom.workspace.open("foo.php"); + editor.setPhpText = setPhpText; + }); + + beforeEach(function () { + this.addMatchers({toHaveScopesAtPosition}); + }); + + describe("loading the grammar", () => { + it('loads the wrapper HTML grammar', () => { + embeddingGrammar = atom.grammars.grammarForScopeName("text.html.php"); + expect(embeddingGrammar).toBeTruthy(); + expect(embeddingGrammar.scopeName).toBe("text.html.php"); + expect(embeddingGrammar.constructor.name).toBe("TreeSitterGrammar"); + // FIXME how to test that all selectors were loaded correctly? Invalid + // selectors may generate errors and it would be great to catch those here. + + // injections + expect(embeddingGrammar.injectionPointsByType.template).toBeTruthy(); + expect(embeddingGrammar.injectionPointsByType.php).toBeTruthy(); + }) + + it('loads the PHP grammar', () => { + embeddedGrammar = atom.grammars.grammarForScopeName("source.php"); + expect(embeddedGrammar).toBeTruthy(); + expect(embeddedGrammar.scopeName).toBe("source.php"); + expect(embeddedGrammar.constructor.name).toBe("TreeSitterGrammar"); + // FIXME how to test that all selectors were loaded correctly? Invalid + // selectors may generate errors and it would be great to catch those here. + + // injections + expect(embeddedGrammar.injectionPointsByType.comment).toBeTruthy(); + }); + }); + + describe("html embedding", () => { + + beforeEach(async () => await atom.packages.activatePackage("language-html")); + + it("handles php wrapped in html", () => { + editor.setText(dedent ` +
+ +
+ `); + + let includingEmbeddedScopes = true + + // FIXME following scopes differ from TM + // BUT this appears to *also* be the case w/ the ERB grammar + // adding a single space before the `<` will correct this + expect(editor).toHaveScopesAtPosition([0, 0], '<', ['text.html.php'], includingEmbeddedScopes) + expect(editor).toHaveScopesAtPosition([0, 1], 'div', ['text.html.php', 'source.html', 'entity.name.tag'], includingEmbeddedScopes) + expect(editor).toHaveScopesAtPosition([0, 4], '>', ['text.html.php', 'source.html', 'punctuation.definition.tag.end'], includingEmbeddedScopes) + + // FIXME checking posn 0 doesn't work, posn 1 *does* + // again, this mostly matches the ERB grammar + // expect(editor).toHaveScopesAtPosition([1, 0], '', ['source.html', 'source.php', 'punctuation.section.embedded.end.php']) + + // FIXME checking posn 0 doesn't work, posn 1 *does* + // expect(editor).toHaveScopesAtPosition([2, 0], '', ['source.html', 'punctuation.definition.tag.end']) + }) + + it("handles php with only leading html", () => { + editor.setText(dedent ` +
+ ', ['text.html.php', 'source.html', 'punctuation.definition.tag.end'], includingEmbeddedScopes) + + // FIXME checking posn 0 doesn't work, posn 1 *does* + // expect(editor).toHaveScopesAtPosition([1, 0], ' { + it("should tokenize = correctly", () => { + editor.setPhpText('$test = 1;'); + + expect(editor).toHaveScopesAtPosition([1, 0], '$', ["source.php", "variable.other.php", "punctuation.definition.variable.php"]); + expect(editor).toHaveScopesAtPosition([1, 5], ' ', ["source.php"]); + expect(editor).toHaveScopesAtPosition([1, 6], '=', ["source.php", "keyword.operator.assignment.php"]); + expect(editor).toHaveScopesAtPosition([1, 8], '1', ["source.php", "constant.numeric.decimal.php"]); + expect(editor).toHaveScopesAtPosition([1, 9], ';', ["source.php", "punctuation.terminator.expression.php"]); + }); + + it("should tokenize + correctly", () => { + editor.setPhpText('1 + 2;'); + + expect(editor).toHaveScopesAtPosition([1, 0], '1', ["source.php", "constant.numeric.decimal.php"]); + expect(editor).toHaveScopesAtPosition([1, 2], '+', ["source.php", "keyword.operator.arithmetic.php"]); + expect(editor).toHaveScopesAtPosition([1, 4], '2', ["source.php", "constant.numeric.decimal.php"]); + }); + + it("should tokenize - correctly", () => { + editor.setPhpText('1 - 2;'); + + expect(editor).toHaveScopesAtPosition([1, 0], '1', ["source.php", "constant.numeric.decimal.php"]); + expect(editor).toHaveScopesAtPosition([1, 2], '-', ["source.php", "keyword.operator.arithmetic.php"]); + expect(editor).toHaveScopesAtPosition([1, 4], '2', ["source.php", "constant.numeric.decimal.php"]); + }); + + it("should tokenize * correctly", () => { + editor.setPhpText('1 * 2;'); + + expect(editor).toHaveScopesAtPosition([1, 0], '1', ["source.php", "constant.numeric.decimal.php"]); + expect(editor).toHaveScopesAtPosition([1, 2], '*', ["source.php", "keyword.operator.arithmetic.php"]); + expect(editor).toHaveScopesAtPosition([1, 4], '2', ["source.php", "constant.numeric.decimal.php"]); + }); + + it("should tokenize / correctly", () => { + editor.setPhpText('1 / 2;'); + + expect(editor).toHaveScopesAtPosition([1, 0], '1', ["source.php", "constant.numeric.decimal.php"]); + expect(editor).toHaveScopesAtPosition([1, 2], '/', ["source.php", "keyword.operator.arithmetic.php"]); + expect(editor).toHaveScopesAtPosition([1, 4], '2', ["source.php", "constant.numeric.decimal.php"]); + }); + + it("should tokenize % correctly", () => { + editor.setPhpText('1 % 2;'); + + expect(editor).toHaveScopesAtPosition([1, 0], '1', ["source.php", "constant.numeric.decimal.php"]); + expect(editor).toHaveScopesAtPosition([1, 2], '%', ["source.php", "keyword.operator.arithmetic.php"]); + expect(editor).toHaveScopesAtPosition([1, 4], '2', ["source.php", "constant.numeric.decimal.php"]); + }); + + it("should tokenize ** correctly", () => { + editor.setPhpText('1 ** 2;'); + + expect(editor).toHaveScopesAtPosition([1, 0], '1', ["source.php", "constant.numeric.decimal.php"]); + expect(editor).toHaveScopesAtPosition([1, 2], '**', ["source.php", "keyword.operator.arithmetic.php"]); + expect(editor).toHaveScopesAtPosition([1, 5], '2', ["source.php", "constant.numeric.decimal.php"]); + }); + + // TODO is this case insensitive? + it("should tokenize instanceof correctly", () => { + editor.setText(dedent` + { + it("should tokenize === correctly", () => { + editor.setText(dedent` + { + editor.setText(dedent` + { + editor.setText(dedent` + { + editor.setText(dedent` + { + editor.setText(dedent` + { + editor.setText(dedent` + { + editor.setText(dedent` + { + editor.setText(dedent` + { + editor.setText(dedent` + { + editor.setText(dedent` + { + editor.setText(dedent` + >= correctly", () => { + editor.setText(dedent` + >= 2; + `); + + expect(editor).toHaveScopesAtPosition([1, 6], ['source.php', "keyword.operator.assignment.php"]); + }); + + it("should tokenize **= correctly", () => { + editor.setText(dedent` + { + editor.setText(dedent` + { + editor.setText(dedent` + { + editor.setPhpText('[0,...$b,2]') + + expect(editor).toHaveScopesAtPosition([1, 0], '[', ['source.php', "punctuation.section.array.begin.php", + // FIXME following scopes differ from TM + 'meta.array.php' + ]); + expect(editor).toHaveScopesAtPosition([1, 3], '...', ['source.php', "keyword.operator.spread.php", + // FIXME following scopes differ from TM + 'meta.array.php' + ]); + expect(editor).toHaveScopesAtPosition([1, 6], '$', ["source.php", "variable.other.php", "punctuation.definition.variable.php", + // FIXME following scopes differ from TM + 'meta.array.php' + ]); + expect(editor).toHaveScopesAtPosition([1, 7], 'b', ['source.php', "variable.other.php", + // FIXME following scopes differ from TM + 'meta.array.php' + ]); + expect(editor).toHaveScopesAtPosition([1, 10],']', ['source.php', "punctuation.section.array.end.php", + // FIXME following scopes differ from TM + 'meta.array.php' + ]); + + editor.setPhpText('test($a, ...$b)'); + await nextHighlightingUpdate(editor) + + expect(editor).toHaveScopesAtPosition([1, 0], ['source.php', "meta.function-call.php", "entity.name.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 4], ["source.php", "meta.function-call.php", "punctuation.definition.arguments.begin.bracket.round.php"]); + expect(editor).toHaveScopesAtPosition([1, 9], ["source.php", "meta.function-call.php", "keyword.operator.spread.php"]); + expect(editor).toHaveScopesAtPosition([1, 12], ["source.php", "meta.function-call.php", "variable.other.php", "punctuation.definition.variable.php"]); + expect(editor).toHaveScopesAtPosition([1, 13], ['source.php', "meta.function-call.php", "variable.other.php"]); + expect(editor).toHaveScopesAtPosition([1, 14], ["source.php", "meta.function-call.php", "punctuation.definition.arguments.end.bracket.round.php"]); + }); + }); + + describe("ternaries", () => { + it("should tokenize ternary expressions", async () => { + editor.setText(dedent` + { + editor.setText(dedent` + { + editor.setText(dedent` + { + editor.setText(dedent` + { + // See https://github.com/atom/language-php/issues/386 + editor.setText(dedent` + {}) + + it("should tokenize $this", async () => { + editor.setPhpText("$this;"); + + expect(editor).toHaveScopesAtPosition([1, 0], ["source.php", "variable.language.this.php", "punctuation.definition.variable.php"]); + expect(editor).toHaveScopesAtPosition([1, 1], ['source.php', "variable.language.this.php"]); + + editor.setPhpText("$thistles;"); + await nextHighlightingUpdate(editor); + + expect(editor).toHaveScopesAtPosition([1, 0], ["source.php", "variable.other.php", "punctuation.definition.variable.php"]); + expect(editor).toHaveScopesAtPosition([1, 1], ['source.php', "variable.other.php"]); + }); + + describe("include", () => { + it("should tokenize include and require correctly", async () => { + editor.setPhpText('include "foo.php";'); + + expect(editor).toHaveScopesAtPosition([1, 0], 'include', ['source.php', "meta.include.php", "keyword.control.import.include.php"]); + expect(editor).toHaveScopesAtPosition([1, 8], '"', ["source.php", "meta.include.php", "string.quoted.double.php", + // FIXME following scopes differ from TM + // "punctuation.definition.string.begin.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 9], 'foo.php', ['source.php', "meta.include.php", "string.quoted.double.php"]); + expect(editor).toHaveScopesAtPosition([1, 16], '"', ["source.php", "meta.include.php", "string.quoted.double.php", + // FIXME following scopes differ from TM + // "punctuation.definition.string.end.php" + ]); + + editor.setPhpText('require "foo.php";'); + await nextHighlightingUpdate(editor); + + expect(editor).toHaveScopesAtPosition([1, 0], ['source.php', "meta.include.php", "keyword.control.import.include.php"]); + expect(editor).toHaveScopesAtPosition([1, 8], ["source.php", "meta.include.php", "string.quoted.double.php", + // FIXME following scopes differ from TM + // "punctuation.definition.string.begin.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 9], ['source.php', "meta.include.php", "string.quoted.double.php"]); + expect(editor).toHaveScopesAtPosition([1, 16], ["source.php", "meta.include.php", "string.quoted.double.php", + // FIXME following scopes differ from TM + // "punctuation.definition.string.end.php" + ]); + }); + + it("should tokenize include_once correctly", () => { + editor.setPhpText('include_once "foo.php";'); + + expect(editor).toHaveScopesAtPosition([1, 0], ['source.php', "meta.include.php", "keyword.control.import.include.php"]); + expect(editor).toHaveScopesAtPosition([1, 13], ["source.php", "meta.include.php", "string.quoted.double.php", + // FIXME following scopes differ from TM + // "punctuation.definition.string.begin.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 14], ['source.php', "meta.include.php", "string.quoted.double.php"]); + expect(editor).toHaveScopesAtPosition([1, 21], ["source.php", "meta.include.php", "string.quoted.double.php", + // FIXME following scopes differ from TM + // "punctuation.definition.string.end.php" + ]); + }); + + it("should tokenize parentheses correctly", () => { + editor.setPhpText('include("foo.php");'); + + expect(editor).toHaveScopesAtPosition([1, 0], ['source.php', "meta.include.php", "keyword.control.import.include.php"]); + expect(editor).toHaveScopesAtPosition([1, 7], ["source.php", "meta.include.php", "punctuation.definition.begin.bracket.round.php"]); + expect(editor).toHaveScopesAtPosition([1, 8], ["source.php", "meta.include.php", "string.quoted.double.php", + // FIXME following scopes differ from TM + // "punctuation.definition.string.begin.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 9], ['source.php', "meta.include.php", "string.quoted.double.php"]); + expect(editor).toHaveScopesAtPosition([1, 16], ["source.php", "meta.include.php", "string.quoted.double.php", + // FIXME following scopes differ from TM + // "punctuation.definition.string.end.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 17], ["source.php", "meta.include.php", "punctuation.definition.end.bracket.round.php"]); + }); + }); + + describe("declaring namespaces", () => { + it("tokenizes namespaces", () => { + editor.setPhpText("namespace Test;"); + + expect(editor).toHaveScopesAtPosition([1, 0], ['source.php', "meta.namespace.php", "keyword.other.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 9], ['source.php', "meta.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 10], ['source.php', "meta.namespace.php", "entity.name.type.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 14], ['source.php', "punctuation.terminator.expression.php", + // FIXME following scopes differ from TM + "meta.namespace.php" + ]); + }); + + it("tokenizes sub-namespaces", () => { + editor.setPhpText("namespace One\\Two\\Three;"); + + expect(editor).toHaveScopesAtPosition([1, 0], ['source.php', "meta.namespace.php", "keyword.other.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 9], ['source.php', "meta.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 10], ['source.php', "meta.namespace.php", "entity.name.type.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 13], ["source.php", "meta.namespace.php", "entity.name.type.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 14], ['source.php', "meta.namespace.php", "entity.name.type.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 17], ["source.php", "meta.namespace.php", "entity.name.type.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 18], ['source.php', "meta.namespace.php", "entity.name.type.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 23], ['source.php', "punctuation.terminator.expression.php", + // FIXME following scopes differ from TM + "meta.namespace.php" + ]); + }); + + // SKIP this is covered by tree-sitter-php, not this grammar + // it("tokenizes namespace with emojis", () => {}) + + it("tokenizes bracketed namespaces", async () => { + editor.setPhpText(` + namespace Test { + // code + } + `); + + expect(editor).toHaveScopesAtPosition([1, 0], ['source.php', "meta.namespace.php", "keyword.other.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 9], ['source.php', "meta.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 10], ['source.php', "meta.namespace.php", "entity.name.type.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 14], ['source.php', "meta.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 15], ["source.php", "meta.namespace.php", "punctuation.definition.namespace.begin.bracket.curly.php"]); + expect(editor).toHaveScopesAtPosition([2, 4], ["source.php", "meta.namespace.php", "comment.line.double-slash.php", + // FIXME following scopes differ from TM + // "punctuation.definition.comment.php" + ]); + expect(editor).toHaveScopesAtPosition([3, 0], ["source.php", "meta.namespace.php", "punctuation.definition.namespace.end.bracket.curly.php"]); + + editor.setPhpText(` + namespace One\\Two\\Three { + // code + } + `); + await nextHighlightingUpdate(editor); + + expect(editor).toHaveScopesAtPosition([1, 0], ['source.php', "meta.namespace.php", "keyword.other.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 9], ['source.php', "meta.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 10], ['source.php', "meta.namespace.php", "entity.name.type.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 13], ["source.php", "meta.namespace.php", "entity.name.type.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 14], ['source.php', "meta.namespace.php", "entity.name.type.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 17], ["source.php", "meta.namespace.php", "entity.name.type.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 18], ['source.php', "meta.namespace.php", "entity.name.type.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 23], ['source.php', "meta.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 24], ["source.php", "meta.namespace.php", "punctuation.definition.namespace.begin.bracket.curly.php"]); + expect(editor).toHaveScopesAtPosition([3, 0], ["source.php", "meta.namespace.php", "punctuation.definition.namespace.end.bracket.curly.php"]); + }); + + it("tokenizes global namespaces", () => { + editor.setPhpText(` + namespace { + // code + } + `); + + expect(editor).toHaveScopesAtPosition([1, 0], ['source.php', "meta.namespace.php", "keyword.other.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 9], ['source.php', "meta.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 10], ["source.php", "meta.namespace.php", "punctuation.definition.namespace.begin.bracket.curly.php"]); + expect(editor).toHaveScopesAtPosition([3, 0], ["source.php", "meta.namespace.php", "punctuation.definition.namespace.end.bracket.curly.php"]); + }); + }); + + describe("use declarations", () => { + it("tokenizes basic use statements", async () => { + editor.setPhpText("use ArrayObject;"); + + expect(editor).toHaveScopesAtPosition([1, 0], 'use', ['source.php', "meta.use.php", "keyword.other.use.php"]); + expect(editor).toHaveScopesAtPosition([1, 3], " ", ['source.php', "meta.use.php"]); + expect(editor).toHaveScopesAtPosition([1, 4], 'ArrayObject', ["source.php", "meta.use.php", "support.class.builtin.php"]); + expect(editor).toHaveScopesAtPosition([1, 15], ';', ['source.php', "punctuation.terminator.expression.php", + // FIXME following scopes differ from TM + "meta.use.php" + ]); + + editor.setPhpText("use My\\Full\\NSname;"); + await nextHighlightingUpdate(editor); + + expect(editor).toHaveScopesAtPosition([1, 0], 'use', ['source.php', "meta.use.php", "keyword.other.use.php"]); + expect(editor).toHaveScopesAtPosition([1, 4], 'My', ['source.php', "meta.use.php", "support.other.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 6], '\\', ["source.php", "meta.use.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 7], 'Full', ['source.php', "meta.use.php", "support.other.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 11], '\\', ["source.php", "meta.use.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 12], 'NSname', ['source.php', "meta.use.php", "support.class.php"]); + expect(editor).toHaveScopesAtPosition([1, 18], ';', ['source.php', "punctuation.terminator.expression.php", + // FIXME following scopes differ from TM + "meta.use.php" + ]); + }); + + // SKIP this is covered by TS-php, not this grammar + // it("tokenizes use statement with emojis", () => {}) + + it("tokenizes multiline use statements", () => { + editor.setPhpText(` + use One\\Two, + Three\\Four; + `); + + expect(editor).toHaveScopesAtPosition([1, 0], ['source.php', "meta.use.php", "keyword.other.use.php"]); + expect(editor).toHaveScopesAtPosition([1, 4], 'One', ['source.php', "meta.use.php", "support.other.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 7], '\\', ["source.php", "meta.use.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 8], 'Two', ['source.php', "meta.use.php", "support.class.php"]); + expect(editor).toHaveScopesAtPosition([1, 11], ',', ['source.php', "meta.use.php", "punctuation.separator.delimiter.php"]); + expect(editor).toHaveScopesAtPosition([2, 4], 'Three', ['source.php', "meta.use.php", "support.other.namespace.php"]); + expect(editor).toHaveScopesAtPosition([2, 9], '\\', ["source.php", "meta.use.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]); + expect(editor).toHaveScopesAtPosition([2, 10], 'Four', ['source.php', "meta.use.php", "support.class.php"]); + expect(editor).toHaveScopesAtPosition([2, 14], ';', ['source.php', "punctuation.terminator.expression.php", + // FIXME following scopes differ from TM + "meta.use.php" + ]); + }); + + it("tokenizes use function statements", () => { + editor.setPhpText("use function My\\Full\\functionName;"); + + expect(editor).toHaveScopesAtPosition([1, 0], 'use', ['source.php', "meta.use.php", "keyword.other.use.php"]); + expect(editor).toHaveScopesAtPosition([1, 4], 'function', ['source.php', "meta.use.php", "storage.type.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 12], ' ', ['source.php', "meta.use.php"]); + expect(editor).toHaveScopesAtPosition([1, 13], 'My', ['source.php', "meta.use.php", "support.other.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 15], '\\', ["source.php", "meta.use.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 16], 'Full', ['source.php', "meta.use.php", "support.other.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 20], '\\', ["source.php", "meta.use.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 21], 'functionName', ['source.php', "meta.use.php", "support.class.php"]); + expect(editor).toHaveScopesAtPosition([1, 33], ';', ['source.php', "punctuation.terminator.expression.php", + // FIXME following scopes differ from TM + "meta.use.php" + ]); + }); + + it("tokenizes use const statements", () => { + editor.setPhpText("use const My\\Full\\CONSTANT;"); + + expect(editor).toHaveScopesAtPosition([1, 0], 'use', ['source.php', "meta.use.php", "keyword.other.use.php"]); + expect(editor).toHaveScopesAtPosition([1, 4], 'const', ['source.php', "meta.use.php", "storage.type.const.php"]); + expect(editor).toHaveScopesAtPosition([1, 9], ' ', ['source.php', "meta.use.php"]); + expect(editor).toHaveScopesAtPosition([1, 10], 'My', ['source.php', "meta.use.php", "support.other.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 12], '\\', ["source.php", "meta.use.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 13], 'Full', ['source.php', "meta.use.php", "support.other.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 17], '\\', ["source.php", "meta.use.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 18], 'CONSTANT', ['source.php', "meta.use.php", "support.class.php"]); + expect(editor).toHaveScopesAtPosition([1, 26], ';', ['source.php', "punctuation.terminator.expression.php", + // FIXME following scopes differ from TM + "meta.use.php" + ]); + }); + + it("tokenizes use-as statements", () => { + editor.setPhpText("use My\\Full\\Classname as Another;"); + + expect(editor).toHaveScopesAtPosition([1, 0], ['source.php', "meta.use.php", "keyword.other.use.php"]); + expect(editor).toHaveScopesAtPosition([1, 4], ['source.php', "meta.use.php", "support.other.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 6], ["source.php", "meta.use.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 7], ['source.php', "meta.use.php", "support.other.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 11], ["source.php", "meta.use.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 12], ['source.php', "meta.use.php", "support.class.php"]); + expect(editor).toHaveScopesAtPosition([1, 21], ' ', ['source.php', "meta.use.php"]); + expect(editor).toHaveScopesAtPosition([1, 22], 'as', ['source.php', "meta.use.php", "keyword.other.use-as.php"]); + expect(editor).toHaveScopesAtPosition([1, 24], ' ', ['source.php', "meta.use.php"]); + expect(editor).toHaveScopesAtPosition([1, 25], 'Another', ['source.php', "meta.use.php", "entity.other.alias.php"]); + expect(editor).toHaveScopesAtPosition([1, 32], ';', ['source.php', "punctuation.terminator.expression.php", + // FIXME following scopes differ from TM + "meta.use.php" + ]); + }); + + it("should tokenize use function as correctly", () => { + editor.setPhpText("use function A\\B\\fun as func;"); + + expect(editor).toHaveScopesAtPosition([1, 0], 'use', ['source.php', "meta.use.php", "keyword.other.use.php"]); + expect(editor).toHaveScopesAtPosition([1, 17], 'fun', ['source.php', "meta.use.php", "support.class.php"]); + expect(editor).toHaveScopesAtPosition([1, 20], ' ', ['source.php', "meta.use.php"]); + expect(editor).toHaveScopesAtPosition([1, 21], 'as', ['source.php', "meta.use.php", "keyword.other.use-as.php"]); + expect(editor).toHaveScopesAtPosition([1, 23], ' ', ['source.php', "meta.use.php"]); + expect(editor).toHaveScopesAtPosition([1, 24], 'func', ['source.php', "meta.use.php", "entity.other.alias.php"]); + }); + + it("tokenizes multiple combined use statements", () => { + editor.setPhpText( + "use My\\Full\\Classname as Another, My\\Full\\NSname;" + ); + + expect(editor).toHaveScopesAtPosition([1, 0], ['source.php', "meta.use.php", "keyword.other.use.php"]); + expect(editor).toHaveScopesAtPosition([1, 4], ['source.php', "meta.use.php", "support.other.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 6], ["source.php", "meta.use.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 7], ['source.php', "meta.use.php", "support.other.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 11], ["source.php", "meta.use.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 12], ['source.php', "meta.use.php", "support.class.php"]); + expect(editor).toHaveScopesAtPosition([1, 21], ' ', ['source.php', "meta.use.php"]); + expect(editor).toHaveScopesAtPosition([1, 22], 'as', ['source.php', "meta.use.php", "keyword.other.use-as.php"]); + expect(editor).toHaveScopesAtPosition([1, 24], ' ', ['source.php', "meta.use.php"]); + expect(editor).toHaveScopesAtPosition([1, 25], 'Another', ['source.php', "meta.use.php", "entity.other.alias.php"]); + expect(editor).toHaveScopesAtPosition([1, 32], ',', ['source.php', "meta.use.php", "punctuation.separator.delimiter.php"]); + expect(editor).toHaveScopesAtPosition([1, 33], ' ', ['source.php', "meta.use.php"]); + expect(editor).toHaveScopesAtPosition([1, 34], 'My', ['source.php', "meta.use.php", "support.other.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 36], '\\', ["source.php", "meta.use.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 37], 'Full', ['source.php', "meta.use.php", "support.other.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 41], '\\', ["source.php", "meta.use.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 42], 'NSname', ['source.php', "meta.use.php", "support.class.php"]); + expect(editor).toHaveScopesAtPosition([1, 48], ['source.php', "punctuation.terminator.expression.php", + // FIXME following scopes differ from TM + "meta.use.php" + ]); + }); + + it("tokenizes grouped use statements", () => { + editor.setPhpText(` + use some\\Namespace\\{ + ClassA, + ClassB, + ClassC as C + }; + `); + + expect(editor).toHaveScopesAtPosition([1, 0], ['source.php', "meta.use.php", "keyword.other.use.php"]); + expect(editor).toHaveScopesAtPosition([1, 4], ['source.php', "meta.use.php", "support.other.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 8], ["source.php", "meta.use.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 9], ['source.php', "meta.use.php", "support.other.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 18], ["source.php", "meta.use.php", + // FIXME following scopes differ from TM + // "support.other.namespace.php", + // "punctuation.separator.inheritance.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 19], '{', ["source.php", "meta.use.php", "punctuation.definition.use.begin.bracket.curly.php"]); + expect(editor).toHaveScopesAtPosition([2, 2], ['source.php', "meta.use.php", "support.class.php"]); + expect(editor).toHaveScopesAtPosition([2, 8], ['source.php', "meta.use.php", "punctuation.separator.delimiter.php"]); + expect(editor).toHaveScopesAtPosition([3, 2], ['source.php', "meta.use.php", "support.class.php"]); + expect(editor).toHaveScopesAtPosition([3, 8], ['source.php', "meta.use.php", "punctuation.separator.delimiter.php"]); + expect(editor).toHaveScopesAtPosition([4, 2], ['source.php', "meta.use.php", "support.class.php"]); + expect(editor).toHaveScopesAtPosition([4, 8], ' ', ['source.php', "meta.use.php"]); + expect(editor).toHaveScopesAtPosition([4, 9], 'as', ['source.php', "meta.use.php", "keyword.other.use-as.php"]); + expect(editor).toHaveScopesAtPosition([4, 11], ' ', ['source.php', "meta.use.php"]); + expect(editor).toHaveScopesAtPosition([4, 12], 'C', ['source.php', "meta.use.php", "entity.other.alias.php"]); + expect(editor).toHaveScopesAtPosition([5, 0], '}', ["source.php", "meta.use.php", "punctuation.definition.use.end.bracket.curly.php"]); + expect(editor).toHaveScopesAtPosition([5, 1], ['source.php', "punctuation.terminator.expression.php", + // FIXME following scopes differ from TM + "meta.use.php" + ]); + }); + + // SKIP this is covered by tree-sitter-php, not this grammar + // it("tokenizes trailing comma in use statements", () => {}); + }); + + describe("classes", () => { + it("tokenizes class declarations", () => { + editor.setPhpText("class Test { /* stuff */ }"); + + expect(editor).toHaveScopesAtPosition([1, 0], ['source.php', "meta.class.php", "storage.type.class.php"]); + expect(editor).toHaveScopesAtPosition([1, 5], ['source.php', "meta.class.php"]); + expect(editor).toHaveScopesAtPosition([1, 6], ['source.php', "meta.class.php", "entity.name.type.class.php"]); + expect(editor).toHaveScopesAtPosition([1, 10], ['source.php', "meta.class.php"]); + expect(editor).toHaveScopesAtPosition([1, 11], ["source.php", "meta.class.php", + // FIXME following scopes differ from TM + "meta.class.body.php", + "punctuation.definition.class.begin.bracket.curly.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 12], ['source.php', "meta.class.php", "meta.class.body.php"]); + expect(editor).toHaveScopesAtPosition([1, 13], '/*', ["source.php", "meta.class.php", "meta.class.body.php", "comment.block.php", + // FIXME following scopes differ from TM + // "punctuation.definition.comment.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 25], ["source.php", "meta.class.php", + // FIXME following scopes differ from TM + "meta.class.body.php", + "punctuation.definition.class.end.bracket.curly.php" + ]); + }); + + it("tokenizes class instantiation", () => { + editor.setPhpText("$a = new ClassName();"); + + expect(editor).toHaveScopesAtPosition([1, 5], 'new', ['source.php', "keyword.other.new.php"]); + expect(editor).toHaveScopesAtPosition([1, 8], ' ', ['source.php']); + expect(editor).toHaveScopesAtPosition([1, 9], 'ClassName', ['source.php', "support.class.php"]); + // FIXME I wonder if punctuation.definition.arguments.begin.bracket.round.php + // would be more correct: these are surrounding arguments to a new instance + expect(editor).toHaveScopesAtPosition([1, 18], '(', ['source.php', "punctuation.definition.begin.bracket.round.php"]); + expect(editor).toHaveScopesAtPosition([1, 19], ')', ['source.php', "punctuation.definition.end.bracket.round.php"]); + expect(editor).toHaveScopesAtPosition([1, 20], ';', ['source.php', "punctuation.terminator.expression.php"]); + }); + + it("tokenizes class modifiers", async () => { + editor.setPhpText("abstract class Test {}"); + + expect(editor).toHaveScopesAtPosition([1, 0], ['source.php', "meta.class.php", "storage.modifier.abstract.php"]); + expect(editor).toHaveScopesAtPosition([1, 8], ['source.php', "meta.class.php"]); + expect(editor).toHaveScopesAtPosition([1, 9], ['source.php', "meta.class.php", "storage.type.class.php"]); + + editor.setPhpText("final class Test {}"); + await nextHighlightingUpdate(editor) + + expect(editor).toHaveScopesAtPosition([1, 0], ['source.php', "meta.class.php", "storage.modifier.final.php"]); + expect(editor).toHaveScopesAtPosition([1, 6], ['source.php', "meta.class.php", "storage.type.class.php"]); + }); + + // SKIP TS-php handles this + // it('tokenizes classes declared immediately after another class ends', () => {}) + + describe("properties", () => { + it("tokenizes types", () => { + editor.setPhpText(` + class A { + public int $a = 1; + } + `); + + expect(editor).toHaveScopesAtPosition([2, 2], 'public', ["source.php", "meta.class.php", "meta.class.body.php", "storage.modifier.php"]); + expect(editor).toHaveScopesAtPosition([2, 9], 'int', ["source.php", "meta.class.php", "meta.class.body.php", "keyword.other.type.php"]); + expect(editor).toHaveScopesAtPosition([2, 13], '$', ["source.php", "meta.class.php", "meta.class.body.php", "variable.other.php", "punctuation.definition.variable.php"]); + expect(editor).toHaveScopesAtPosition([2, 14], 'a', ["source.php", "meta.class.php", "meta.class.body.php", "variable.other.php"]); + }); + + it("tokenizes nullable types", async () => { + editor.setPhpText(` + class A { + static ?string $b = 'Bee'; + } + `) + + expect(editor).toHaveScopesAtPosition([2, 2], 'static', ["source.php", "meta.class.php", "meta.class.body.php", "storage.modifier.php"]) + expect(editor).toHaveScopesAtPosition([2, 9], '?', ["source.php", "meta.class.php", "meta.class.body.php", "keyword.operator.nullable-type.php"]) + expect(editor).toHaveScopesAtPosition([2, 10], 'string', ["source.php", "meta.class.php", "meta.class.body.php", "keyword.other.type.php"]) + expect(editor).toHaveScopesAtPosition([2, 17], '$', ["source.php", "meta.class.php", "meta.class.body.php", "variable.other.php", "punctuation.definition.variable.php"]) + expect(editor).toHaveScopesAtPosition([2, 18], 'b', ["source.php", "meta.class.php", "meta.class.body.php", "variable.other.php"]) + + editor.setPhpText(` + class A { + static? string $b; + } + `) + await nextHighlightingUpdate(editor) + + expect(editor).toHaveScopesAtPosition([2, 2], 'static', ["source.php", "meta.class.php", "meta.class.body.php", "storage.modifier.php"]) + expect(editor).toHaveScopesAtPosition([2, 8], '?', ["source.php", "meta.class.php", "meta.class.body.php", "keyword.operator.nullable-type.php"]) + expect(editor).toHaveScopesAtPosition([2, 10], 'string', ["source.php", "meta.class.php", "meta.class.body.php", "keyword.other.type.php"]) + + }) + + it("tokenizes union types", () => { + editor.setPhpText(` + class A { + public int|string $id; + } + `) + + expect(editor).toHaveScopesAtPosition([2, 2], 'public', ['source.php', 'meta.class.php', 'meta.class.body.php', 'storage.modifier.php']) + expect(editor).toHaveScopesAtPosition([2, 9], 'int', ['source.php', 'meta.class.php', 'meta.class.body.php', 'keyword.other.type.php']) + expect(editor).toHaveScopesAtPosition([2, 12], '|', ['source.php', 'meta.class.php', 'meta.class.body.php', 'punctuation.separator.delimiter.php']) + expect(editor).toHaveScopesAtPosition([2, 13], 'string', ['source.php', 'meta.class.php', 'meta.class.body.php', 'keyword.other.type.php']) + expect(editor).toHaveScopesAtPosition([2, 20], '$', ['source.php', 'meta.class.php', 'meta.class.body.php', 'variable.other.php', 'punctuation.definition.variable.php']) + expect(editor).toHaveScopesAtPosition([2, 21], 'id', ['source.php', 'meta.class.php', 'meta.class.body.php', 'variable.other.php']) + + }) + + // FIXME TS-php doesn't support intersection types yet + xit("tokenizes intersection types", () => { + editor.setPhpText(` + class A { + public FooInterface & BarInterface $foobar; + } + `) + + expect(editor).toHaveScopesAtPosition([2, 2], 'public', ['source.php', 'meta.class.php', 'meta.class.body.php', 'storage.modifier.php']) + expect(editor).toHaveScopesAtPosition([2, 9], 'FooInterface', ['source.php', 'meta.class.php', 'meta.class.body.php', 'support.class.php']) + expect(editor).toHaveScopesAtPosition([2, 22], '&', ['source.php', 'meta.class.php', 'meta.class.body.php', 'punctuation.separator.delimiter.php']) + expect(editor).toHaveScopesAtPosition([2, 24], 'BarInterface', ['source.php', 'meta.class.php', 'meta.class.body.php', 'support.class.php']) + expect(editor).toHaveScopesAtPosition([2, 37], '$', ['source.php', 'meta.class.php', 'meta.class.body.php', 'variable.other.php', 'punctuation.definition.variable.php']) + expect(editor).toHaveScopesAtPosition([2, 38], 'foobar', ['source.php', 'meta.class.php', 'meta.class.body.php', 'variable.other.php']) + + }) + + it("tokenizes 2 modifiers correctly", () => { + editor.setPhpText(` + class Foo { + public static $bar = 'baz'; + } + `); + + expect(editor).toHaveScopesAtPosition([2, 2], 'public', ["source.php", "meta.class.php", "meta.class.body.php", "storage.modifier.php"]); + expect(editor).toHaveScopesAtPosition([2, 9], 'static', ["source.php", "meta.class.php", "meta.class.body.php", "storage.modifier.php"]); + expect(editor).toHaveScopesAtPosition([2, 16], '$', ["source.php", "meta.class.php", "meta.class.body.php", "variable.other.php", "punctuation.definition.variable.php"]); + expect(editor).toHaveScopesAtPosition([2, 17], 'bar', ["source.php", "meta.class.php", "meta.class.body.php", "variable.other.php"]); + }); + + it('tokenizes namespaces', () => { + editor.setPhpText(` + class A { + public ?\\Space\\Test $c; + } + `) + + expect(editor).toHaveScopesAtPosition([2, 2], 'public', ["source.php", "meta.class.php", "meta.class.body.php", "storage.modifier.php"]) + expect(editor).toHaveScopesAtPosition([2, 9], '?', ["source.php", "meta.class.php", "meta.class.body.php", "keyword.operator.nullable-type.php"]) + expect(editor).toHaveScopesAtPosition([2, 10], '\\', ["source.php", "meta.class.php", "meta.class.body.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]) + expect(editor).toHaveScopesAtPosition([2, 11], 'Space', ["source.php", "meta.class.php", "meta.class.body.php", "support.other.namespace.php"]) + expect(editor).toHaveScopesAtPosition([2, 16], '\\', ["source.php", "meta.class.php", "meta.class.body.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]) + expect(editor).toHaveScopesAtPosition([2, 17], 'Test', ["source.php", "meta.class.php", "meta.class.body.php", "support.class.php"]) + expect(editor).toHaveScopesAtPosition([2, 22], '$', ["source.php", "meta.class.php", "meta.class.body.php", "variable.other.php", "punctuation.definition.variable.php"]) + expect(editor).toHaveScopesAtPosition([2, 23], 'c', ["source.php", "meta.class.php", "meta.class.body.php", "variable.other.php"]) + + }); + + it("tokenizes multiple properties", () => { + editor.setPhpText(` + class A { + static int $a = 1; + public \\Other\\Type $b; + private static ? array $c1, $c2; + } + `); + + expect(editor).toHaveScopesAtPosition([2, 2], 'static', ["source.php", "meta.class.php", "meta.class.body.php", "storage.modifier.php"]) + expect(editor).toHaveScopesAtPosition([2, 9], 'int', ["source.php", "meta.class.php", "meta.class.body.php", "keyword.other.type.php"]) + expect(editor).toHaveScopesAtPosition([2, 13], '$', ["source.php", "meta.class.php", "meta.class.body.php", "variable.other.php", "punctuation.definition.variable.php"]) + expect(editor).toHaveScopesAtPosition([2, 14], 'a', ["source.php", "meta.class.php", "meta.class.body.php", "variable.other.php"]) + + expect(editor).toHaveScopesAtPosition([3, 2], 'public', ["source.php", "meta.class.php", "meta.class.body.php", "storage.modifier.php"]) + expect(editor).toHaveScopesAtPosition([3, 9], '\\', ["source.php", "meta.class.php", "meta.class.body.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]) + expect(editor).toHaveScopesAtPosition([3, 10], 'Other', ["source.php", "meta.class.php", "meta.class.body.php", "support.other.namespace.php"]) + expect(editor).toHaveScopesAtPosition([3, 15], '\\', ["source.php", "meta.class.php", "meta.class.body.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]) + expect(editor).toHaveScopesAtPosition([3, 16], 'Type', ["source.php", "meta.class.php", "meta.class.body.php", "support.class.php"]) + expect(editor).toHaveScopesAtPosition([3, 21], '$', ["source.php", "meta.class.php", "meta.class.body.php", "variable.other.php", "punctuation.definition.variable.php"]) + expect(editor).toHaveScopesAtPosition([3, 22], 'b', ["source.php", "meta.class.php", "meta.class.body.php", "variable.other.php"]) + + expect(editor).toHaveScopesAtPosition([4, 2], 'private', ["source.php", "meta.class.php", "meta.class.body.php", "storage.modifier.php"]); + expect(editor).toHaveScopesAtPosition([4, 10], 'static', ["source.php", "meta.class.php", "meta.class.body.php", "storage.modifier.php"]); + expect(editor).toHaveScopesAtPosition([4, 17], '?', ["source.php", "meta.class.php", "meta.class.body.php", "keyword.operator.nullable-type.php"]) + expect(editor).toHaveScopesAtPosition([4, 19], 'array', ["source.php", "meta.class.php", "meta.class.body.php", "keyword.other.type.php"]) + expect(editor).toHaveScopesAtPosition([4, 25], '$', ["source.php", "meta.class.php", "meta.class.body.php", "variable.other.php", "punctuation.definition.variable.php"]); + expect(editor).toHaveScopesAtPosition([4, 26], 'c1', ["source.php", "meta.class.php", "meta.class.body.php", "variable.other.php"]); + expect(editor).toHaveScopesAtPosition([4, 30], '$', ["source.php", "meta.class.php", "meta.class.body.php", "variable.other.php", "punctuation.definition.variable.php"]); + expect(editor).toHaveScopesAtPosition([4, 31], 'c2', ["source.php", "meta.class.php", "meta.class.body.php", "variable.other.php"]); + }); + }); + + describe("methods", () => { + it("tokenizes basic method", () => { + editor.setPhpText(` + class Test { + public function Run($a, $b = false){} + } + `); + + expect(editor).toHaveScopesAtPosition([2, 2], 'public', ["source.php", "meta.class.php", "meta.class.body.php", "meta.function.php", "storage.modifier.php"]); + expect(editor).toHaveScopesAtPosition([2, 9], 'function', ["source.php", "meta.class.php", "meta.class.body.php", "meta.function.php", "storage.type.function.php"]); + expect(editor).toHaveScopesAtPosition([2, 18], 'Run', ["source.php", "meta.class.php", "meta.class.body.php", "meta.function.php", "entity.name.function.php"]); + expect(editor).toHaveScopesAtPosition([2, 21], '(', ["source.php", "meta.class.php", "meta.class.body.php", "meta.function.php", "punctuation.definition.parameters.begin.bracket.round.php", + // FIXME following scopes differ from TM + "meta.function.parameters.php", + ]); + expect(editor).toHaveScopesAtPosition([2, 22], '$', ["source.php", "meta.class.php", "meta.class.body.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.no-default.php", "variable.other.php", "punctuation.definition.variable.php"]); + expect(editor).toHaveScopesAtPosition([2, 23], 'a', ["source.php", "meta.class.php", "meta.class.body.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.no-default.php", "variable.other.php"]); + expect(editor).toHaveScopesAtPosition([2, 24], ',', ["source.php", "meta.class.php", "meta.class.body.php", "meta.function.php", "meta.function.parameters.php", "punctuation.separator.delimiter.php"]); + expect(editor).toHaveScopesAtPosition([2, 26], '$', ["source.php", "meta.class.php", "meta.class.body.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", "variable.other.php", "punctuation.definition.variable.php"]); + expect(editor).toHaveScopesAtPosition([2, 27], 'b', ["source.php", "meta.class.php", "meta.class.body.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", "variable.other.php"]); + expect(editor).toHaveScopesAtPosition([2, 29], '=', ["source.php", "meta.class.php", "meta.class.body.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", "keyword.operator.assignment.php"]); + expect(editor).toHaveScopesAtPosition([2, 31], 'false', ["source.php", "meta.class.php", "meta.class.body.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", "constant.language.php"]); + }); + + it("tokenizes typehinted method", () => { + editor.setPhpText(` + class Test { + public function Run(int $a, ? ClassB $b, self | bool $c = false) : float | ClassA {} + } + `); + + // FIXME following scopes differ from TM + // this grammar doesn't support "meta.function.parameter.typehinted.php" + // b/c limitations in how Atom TS selectors work. In all of these + // expectations, anything w/ a scope of "meta.function.parameters.php" + // whould have "meta.function.parameter.typehinted.php" in the TM + // grammar, but has either "meta.function.parameter.no-default.php" or + // ... "default" in this grammar + expect(editor).toHaveScopesAtPosition([2, 22], 'int', ["source.php", "meta.class.php", "meta.class.body.php", "meta.function.php", "meta.function.parameters.php", "keyword.other.type.php", "meta.function.parameter.no-default.php"]); + expect(editor).toHaveScopesAtPosition([2, 26], '$', ["source.php", "meta.class.php", "meta.class.body.php", "meta.function.php", "meta.function.parameters.php", "variable.other.php", "punctuation.definition.variable.php", "meta.function.parameter.no-default.php"]); + expect(editor).toHaveScopesAtPosition([2, 27], 'a', ["source.php", "meta.class.php", "meta.class.body.php", "meta.function.php", "meta.function.parameters.php", "variable.other.php", "meta.function.parameter.no-default.php"]); + expect(editor).toHaveScopesAtPosition([2, 30], '?', ["source.php", "meta.class.php", "meta.class.body.php", "meta.function.php", "meta.function.parameters.php", "keyword.operator.nullable-type.php", "meta.function.parameter.no-default.php"]); + expect(editor).toHaveScopesAtPosition([2, 32], 'ClassB', ["source.php", "meta.class.php", "meta.class.body.php", "meta.function.php", "meta.function.parameters.php", "support.class.php", "meta.function.parameter.no-default.php"]); + expect(editor).toHaveScopesAtPosition([2, 39], '$', ["source.php", "meta.class.php", "meta.class.body.php", "meta.function.php", "meta.function.parameters.php", "variable.other.php", "punctuation.definition.variable.php", "meta.function.parameter.no-default.php"]); + expect(editor).toHaveScopesAtPosition([2, 40], 'b', ["source.php", "meta.class.php", "meta.class.body.php", "meta.function.php", "meta.function.parameters.php", "variable.other.php", "meta.function.parameter.no-default.php"]); + expect(editor).toHaveScopesAtPosition([2, 43], 'self', ["source.php", "meta.class.php", "meta.class.body.php", "meta.function.php", "meta.function.parameters.php", "storage.type.php", "meta.function.parameter.default.php"]); + expect(editor).toHaveScopesAtPosition([2, 48], '|', ["source.php", "meta.class.php", "meta.class.body.php", "meta.function.php", "meta.function.parameters.php", "punctuation.separator.delimiter.php", "meta.function.parameter.default.php"]); + expect(editor).toHaveScopesAtPosition([2, 50], 'bool', ["source.php", "meta.class.php", "meta.class.body.php", "meta.function.php", "meta.function.parameters.php", "keyword.other.type.php", "meta.function.parameter.default.php"]); + expect(editor).toHaveScopesAtPosition([2, 55], '$', ["source.php", "meta.class.php", "meta.class.body.php", "meta.function.php", "meta.function.parameters.php", "variable.other.php", "punctuation.definition.variable.php", "meta.function.parameter.default.php"]); + expect(editor).toHaveScopesAtPosition([2, 56], 'c', ["source.php", "meta.class.php", "meta.class.body.php", "meta.function.php", "meta.function.parameters.php", "variable.other.php", "meta.function.parameter.default.php"]); + expect(editor).toHaveScopesAtPosition([2, 58], '=', ["source.php", "meta.class.php", "meta.class.body.php", "meta.function.php", "meta.function.parameters.php", "keyword.operator.assignment.php", "meta.function.parameter.default.php"]); + expect(editor).toHaveScopesAtPosition([2, 60], 'false', ["source.php", "meta.class.php", "meta.class.body.php", "meta.function.php", "meta.function.parameters.php", "constant.language.php", "meta.function.parameter.default.php"]); + expect(editor).toHaveScopesAtPosition([2, 67], ':', ["source.php", "meta.class.php", "meta.class.body.php", "meta.function.php", "keyword.operator.return-value.php"]); + expect(editor).toHaveScopesAtPosition([2, 69], 'float', ["source.php", "meta.class.php", "meta.class.body.php", "meta.function.php", "keyword.other.type.php"]); + expect(editor).toHaveScopesAtPosition([2, 75], '|', ["source.php", "meta.class.php", "meta.class.body.php", "meta.function.php", "punctuation.separator.delimiter.php"]); + expect(editor).toHaveScopesAtPosition([2, 77], 'ClassA', ["source.php", "meta.class.php", "meta.class.body.php", "meta.function.php", "support.class.php"]); + }); + + it("tokenizes static return type", () => { + editor.setPhpText(` + class Test { + public function Me() :static {} + } + `); + + expect(editor).toHaveScopesAtPosition([2, 23], ':', ["source.php", "meta.class.php", "meta.class.body.php", "meta.function.php", "keyword.operator.return-value.php"]); + expect(editor).toHaveScopesAtPosition([2, 24], 'static', ["source.php", "meta.class.php", "meta.class.body.php", "meta.function.php", "storage.type.php"]); + }); + + it('tokenizes basic promoted properties in constructor', () => { + editor.setPhpText(` + class Test { + public function __construct(public $a, public int $b = 1) {} + } + `) + + expect(editor).toHaveScopesAtPosition([2, 22], '__construct', ['source.php', 'meta.class.php', 'meta.class.body.php', 'meta.function.php', 'support.function.constructor.php']) + expect(editor).toHaveScopesAtPosition([2, 30], 'public', ['source.php', 'meta.class.php', 'meta.class.body.php', 'meta.function.php', 'meta.function.parameters.php', 'meta.function.parameter.promoted-property.php', 'storage.modifier.php']) + expect(editor).toHaveScopesAtPosition([2, 37], '$', ['source.php', 'meta.class.php', 'meta.class.body.php', 'meta.function.php', 'meta.function.parameters.php', 'meta.function.parameter.promoted-property.php', 'variable.other.php', 'punctuation.definition.variable.php']) + expect(editor).toHaveScopesAtPosition([2, 38], 'a', ['source.php', 'meta.class.php', 'meta.class.body.php', 'meta.function.php', 'meta.function.parameters.php', 'meta.function.parameter.promoted-property.php', 'variable.other.php']) + expect(editor).toHaveScopesAtPosition([2, 41], 'public', ['source.php', 'meta.class.php', 'meta.class.body.php', 'meta.function.php', 'meta.function.parameters.php', 'meta.function.parameter.promoted-property.php', 'storage.modifier.php']) + expect(editor).toHaveScopesAtPosition([2, 48], 'int', ['source.php', 'meta.class.php', 'meta.class.body.php', 'meta.function.php', 'meta.function.parameters.php', 'meta.function.parameter.promoted-property.php', 'keyword.other.type.php']) + expect(editor).toHaveScopesAtPosition([2, 52], '$', ['source.php', 'meta.class.php', 'meta.class.body.php', 'meta.function.php', 'meta.function.parameters.php', 'meta.function.parameter.promoted-property.php', 'variable.other.php', 'punctuation.definition.variable.php']) + expect(editor).toHaveScopesAtPosition([2, 53], 'b', ['source.php', 'meta.class.php', 'meta.class.body.php', 'meta.function.php', 'meta.function.parameters.php', 'meta.function.parameter.promoted-property.php', 'variable.other.php']) + expect(editor).toHaveScopesAtPosition([2, 55], '=', ['source.php', 'meta.class.php', 'meta.class.body.php', 'meta.function.php', 'meta.function.parameters.php', 'meta.function.parameter.promoted-property.php', 'keyword.operator.assignment.php']) + expect(editor).toHaveScopesAtPosition([2, 57], '1', ['source.php', 'meta.class.php', 'meta.class.body.php', 'meta.function.php', 'meta.function.parameters.php', 'meta.function.parameter.promoted-property.php', 'constant.numeric.decimal.php']) + }) + + it('tokenizes promoted properties with parameters in constructor', () => { + editor.setPhpText(` + class Test { + public function __construct(public bool $a, string $b) {} + } + `) + + expect(editor).toHaveScopesAtPosition([2, 22], '__construct', ['source.php', 'meta.class.php', 'meta.class.body.php', 'meta.function.php', 'support.function.constructor.php']) + expect(editor).toHaveScopesAtPosition([2, 30], 'public', ['source.php', 'meta.class.php', 'meta.class.body.php', 'meta.function.php', 'meta.function.parameters.php', 'meta.function.parameter.promoted-property.php', 'storage.modifier.php']) + expect(editor).toHaveScopesAtPosition([2, 37], 'bool', ['source.php', 'meta.class.php', 'meta.class.body.php', 'meta.function.php', 'meta.function.parameters.php', 'meta.function.parameter.promoted-property.php', 'keyword.other.type.php']) + expect(editor).toHaveScopesAtPosition([2, 42], '$', ['source.php', 'meta.class.php', 'meta.class.body.php', 'meta.function.php', 'meta.function.parameters.php', 'meta.function.parameter.promoted-property.php', 'variable.other.php', 'punctuation.definition.variable.php']) + expect(editor).toHaveScopesAtPosition([2, 43], 'a', ['source.php', 'meta.class.php', 'meta.class.body.php', 'meta.function.php', 'meta.function.parameters.php', 'meta.function.parameter.promoted-property.php', 'variable.other.php']) + expect(editor).toHaveScopesAtPosition([2, 46], 'string', ['source.php', 'meta.class.php', 'meta.class.body.php', 'meta.function.php', 'meta.function.parameters.php', 'keyword.other.type.php', + // FIXME following scopes differ from TM + // 'meta.function.parameter.typehinted.php', + 'meta.function.parameter.no-default.php' + ]) + expect(editor).toHaveScopesAtPosition([2, 53], '$', ['source.php', 'meta.class.php', 'meta.class.body.php', 'meta.function.php', 'meta.function.parameters.php', 'variable.other.php', 'punctuation.definition.variable.php', + // FIXME following scopes differ from TM + // 'meta.function.parameter.typehinted.php', + 'meta.function.parameter.no-default.php' + ]) + expect(editor).toHaveScopesAtPosition([2, 54], 'b', ['source.php', 'meta.class.php', 'meta.class.body.php', 'meta.function.php', 'meta.function.parameters.php', 'variable.other.php', + // FIXME following scopes differ from TM + // 'meta.function.parameter.typehinted.php', + 'meta.function.parameter.no-default.php' + ]) + }) + + // FIXME TS-php doesn't support readonly properties yet + xit('tokenizes readonly promoted properties', () => { + editor.setPhpText(` + class Test { + public function __construct(public readonly int $a, readonly protected? string $b) {} + } + `) + + expect(editor).toHaveScopesAtPosition([2, 22], '__construct', ['source.php', 'meta.class.php', 'meta.class.body.php', 'meta.function.php', 'support.function.constructor.php']) + expect(editor).toHaveScopesAtPosition([2, 30], 'public', ['source.php', 'meta.class.php', 'meta.class.body.php', 'meta.function.php', 'meta.function.parameters.php', 'meta.function.parameter.promoted-property.php', 'storage.modifier.php']) + expect(editor).toHaveScopesAtPosition([2, 37], 'readonly', ['source.php', 'meta.class.php', 'meta.class.body.php', 'meta.function.php', 'meta.function.parameters.php', 'meta.function.parameter.promoted-property.php', 'storage.modifier.php']) + expect(editor).toHaveScopesAtPosition([2, 46], 'int', ['source.php', 'meta.class.php', 'meta.class.body.php', 'meta.function.php', 'meta.function.parameters.php', 'meta.function.parameter.promoted-property.php', 'keyword.other.type.php']) + expect(editor).toHaveScopesAtPosition([2, 50], '$', ['source.php', 'meta.class.php', 'meta.class.body.php', 'meta.function.php', 'meta.function.parameters.php', 'meta.function.parameter.promoted-property.php', 'variable.other.php', 'punctuation.definition.variable.php']) + expect(editor).toHaveScopesAtPosition([2, 51], 'a', ['source.php', 'meta.class.php', 'meta.class.body.php', 'meta.function.php', 'meta.function.parameters.php', 'meta.function.parameter.promoted-property.php', 'variable.other.php']) + expect(editor).toHaveScopesAtPosition([2, 54], 'readonly', ['source.php', 'meta.class.php', 'meta.class.body.php', 'meta.function.php', 'meta.function.parameters.php', 'meta.function.parameter.promoted-property.php', 'storage.modifier.php']) + expect(editor).toHaveScopesAtPosition([2, 63], 'protected', ['source.php', 'meta.class.php', 'meta.class.body.php', 'meta.function.php', 'meta.function.parameters.php', 'meta.function.parameter.promoted-property.php', 'storage.modifier.php']) + expect(editor).toHaveScopesAtPosition([2, 72], '?', ['source.php', 'meta.class.php', 'meta.class.body.php', 'meta.function.php', 'meta.function.parameters.php', 'meta.function.parameter.promoted-property.php', 'keyword.operator.nullable-type.php']) + expect(editor).toHaveScopesAtPosition([2, 74], 'string', ['source.php', 'meta.class.php', 'meta.class.body.php', 'meta.function.php', 'meta.function.parameters.php', 'meta.function.parameter.promoted-property.php', 'keyword.other.type.php']) + expect(editor).toHaveScopesAtPosition([2, 81], '$', ['source.php', 'meta.class.php', 'meta.class.body.php', 'meta.function.php', 'meta.function.parameters.php', 'meta.function.parameter.promoted-property.php', 'variable.other.php', 'punctuation.definition.variable.php']) + expect(editor).toHaveScopesAtPosition([2, 82], 'b', ['source.php', 'meta.class.php', 'meta.class.body.php', 'meta.function.php', 'meta.function.parameters.php', 'meta.function.parameter.promoted-property.php', 'variable.other.php']) + }) + + // FIXME Uhhh ... to accomplish this we would have to recognize + // method_declarations w/ a name of "__construct" that also + // have a return_type of primitive_type. Either of these is easy, but both + // of them together *may* be impossible w/ Atom's current TS selector API + xit("tokenizes constructor with illegal return type declaration", () => { + editor.setPhpText(` + class Test { + public function __construct() : int {} + } + `); + + expect(editor).toHaveScopesAtPosition([2, 18], '__construct', ["source.php", "meta.class.php", "meta.class.body.php", "meta.function.php", "support.function.constructor.php"]); + expect(editor).toHaveScopesAtPosition([2, 32], ':', ["source.php", "meta.class.php", "meta.class.body.php", "meta.function.php", "invalid.illegal.return-type.php"]); + }); + }); + + describe("use statements", () => { + it("tokenizes basic use statements", async () => { + editor.setPhpText(` + class Test { + use A; + } + `); + + expect(editor).toHaveScopesAtPosition([2, 2], 'use', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "keyword.other.use.php"]); + expect(editor).toHaveScopesAtPosition([2, 5], ' ', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php"]); + expect(editor).toHaveScopesAtPosition([2, 6], 'A', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "support.class.php"]); + expect(editor).toHaveScopesAtPosition([2, 7], ';', ["source.php", "meta.class.php", "meta.class.body.php", "punctuation.terminator.expression.php", + // FIXME following scopes differ from TM + "meta.use.php" + ]); + + editor.setPhpText(` + class Test { + use A, B; + } + `); + await nextHighlightingUpdate(editor) + + expect(editor).toHaveScopesAtPosition([2, 2], 'use', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "keyword.other.use.php"]); + expect(editor).toHaveScopesAtPosition([2, 5], ' ', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php"]); + expect(editor).toHaveScopesAtPosition([2, 6], 'A', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "support.class.php"]); + expect(editor).toHaveScopesAtPosition([2, 7], ',', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "punctuation.separator.delimiter.php"]); + expect(editor).toHaveScopesAtPosition([2, 8], ' ', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php"]); + expect(editor).toHaveScopesAtPosition([2, 9], 'B', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "support.class.php"]); + expect(editor).toHaveScopesAtPosition([2, 10], ';', ["source.php", "meta.class.php", "meta.class.body.php", "punctuation.terminator.expression.php", + // FIXME following scopes differ from TM + "meta.use.php" + ]); + + editor.setPhpText(` + class Test { + use A\\B; + } + `); + await nextHighlightingUpdate(editor) + + expect(editor).toHaveScopesAtPosition([2, 2], 'use', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "keyword.other.use.php"]); + expect(editor).toHaveScopesAtPosition([2, 5], ' ', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php"]); + expect(editor).toHaveScopesAtPosition([2, 6], 'A', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "support.other.namespace.php"]); + expect(editor).toHaveScopesAtPosition([2, 7], '\\', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]); + expect(editor).toHaveScopesAtPosition([2, 8], 'B', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "support.class.php"]); + expect(editor).toHaveScopesAtPosition([2, 9], ';', ["source.php", "meta.class.php", "meta.class.body.php", "punctuation.terminator.expression.php", + // FIXME following scopes differ from TM + "meta.use.php" + ]); + }); + + it("tokenizes multiline use statements", () => { + editor.setPhpText(` + class Test { + use One\\Two, + Three\\Four; + } + `); + + expect(editor).toHaveScopesAtPosition([2, 2], 'use', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "keyword.other.use.php"]); + expect(editor).toHaveScopesAtPosition([2, 6], 'One', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "support.other.namespace.php"]); + expect(editor).toHaveScopesAtPosition([2, 9], '\\', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]); + expect(editor).toHaveScopesAtPosition([2, 10], 'Two', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "support.class.php"]); + expect(editor).toHaveScopesAtPosition([2, 13], ',', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "punctuation.separator.delimiter.php"]); + expect(editor).toHaveScopesAtPosition([3, 6], 'Three', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "support.other.namespace.php"]); + expect(editor).toHaveScopesAtPosition([3, 11], '\\', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]); + expect(editor).toHaveScopesAtPosition([3, 12], 'Four', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "support.class.php"]); + expect(editor).toHaveScopesAtPosition([3, 16], ';', ["source.php", "meta.class.php", "meta.class.body.php", "punctuation.terminator.expression.php", + // FIXME following scopes differ from TM + "meta.use.php" + ]); + }); + + it("tokenizes complex use statements", () => { + editor.setPhpText(` + class Test { + use A, B { + B::smallTalk insteadof A; + } + /* comment */ + } + `); + + expect(editor).toHaveScopesAtPosition([2, 2], 'use', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "keyword.other.use.php"]); + expect(editor).toHaveScopesAtPosition([2, 5], ' ', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php"]); + expect(editor).toHaveScopesAtPosition([2, 6], 'A', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "support.class.php"]); + expect(editor).toHaveScopesAtPosition([2, 7], ',', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "punctuation.separator.delimiter.php"]); + expect(editor).toHaveScopesAtPosition([2, 8], ' ', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php"]); + expect(editor).toHaveScopesAtPosition([2, 9], 'B', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "support.class.php"]); + expect(editor).toHaveScopesAtPosition([2, 10], ' ', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php"]); + expect(editor).toHaveScopesAtPosition([2, 11], '{', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "punctuation.definition.use.begin.bracket.curly.php"]); + + expect(editor).toHaveScopesAtPosition([3, 4], 'B', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "support.class.php"]); + expect(editor).toHaveScopesAtPosition([3, 5], '::', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "keyword.operator.class.php"]); + expect(editor).toHaveScopesAtPosition([3, 7], 'smallTalk', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "constant.other.class.php"]); + expect(editor).toHaveScopesAtPosition([3, 16], ' ', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php"]); + expect(editor).toHaveScopesAtPosition([3, 17], 'insteadof', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "keyword.other.use-insteadof.php"]); + expect(editor).toHaveScopesAtPosition([3, 26], ' ', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php"]); + expect(editor).toHaveScopesAtPosition([3, 27], 'A', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "support.class.php"]); + expect(editor).toHaveScopesAtPosition([3, 28], ';', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "punctuation.terminator.expression.php"]); + + expect(editor).toHaveScopesAtPosition([4, 2], '}', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "punctuation.definition.use.end.bracket.curly.php"]); + expect(editor).toHaveScopesAtPosition([5, 2], '/*', ["source.php", "meta.class.php", "meta.class.body.php", "comment.block.php", + // FIXME following scopes differ from TM + // "punctuation.definition.comment.php" + ]); + }); + + it("tokenizes aliases", () => { + editor.setPhpText(` + class Aliased_Talker { + use A, B { + B::smallTalk as private talk; + } + } + `); + + expect(editor).toHaveScopesAtPosition([2, 11], 'B', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "support.class.php"]); + + expect(editor).toHaveScopesAtPosition([3, 9], '::', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "keyword.operator.class.php"]); + expect(editor).toHaveScopesAtPosition([3, 11], 'smallTalk', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "constant.other.class.php"]); + expect(editor).toHaveScopesAtPosition([3, 20], ' ', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php"]); + expect(editor).toHaveScopesAtPosition([3, 21], 'as', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "keyword.other.use-as.php"]); + expect(editor).toHaveScopesAtPosition([3, 23], ' ', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php"]); + expect(editor).toHaveScopesAtPosition([3, 24], 'private', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "storage.modifier.php"]); + expect(editor).toHaveScopesAtPosition([3, 31], ' ', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php"]); + expect(editor).toHaveScopesAtPosition([3, 32], 'talk', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "entity.other.alias.php"]); + expect(editor).toHaveScopesAtPosition([3, 36], ';', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "punctuation.terminator.expression.php"]); + + expect(editor).toHaveScopesAtPosition([4, 4], '}', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "punctuation.definition.use.end.bracket.curly.php"]); + }); + + it("tokenizes aliases", () => { + editor.setPhpText(` + class Aliased_Talker { + use A, B { + B::smallTalk as talk; + } + } + `); + + expect(editor).toHaveScopesAtPosition([3, 8], 'B', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "support.class.php"]); + expect(editor).toHaveScopesAtPosition([3, 9], '::', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "keyword.operator.class.php"]); + expect(editor).toHaveScopesAtPosition([3, 11], 'smallTalk', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "constant.other.class.php"]); + expect(editor).toHaveScopesAtPosition([3, 20], ' ', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php"]); + expect(editor).toHaveScopesAtPosition([3, 21], 'as', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "keyword.other.use-as.php"]); + expect(editor).toHaveScopesAtPosition([3, 23], ' ', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php"]); + expect(editor).toHaveScopesAtPosition([3, 27], 'talk', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "entity.other.alias.php"]); + expect(editor).toHaveScopesAtPosition([3, 31], ';', ["source.php", "meta.class.php", "meta.class.body.php", "meta.use.php", "punctuation.terminator.expression.php"]); + + expect(editor).toHaveScopesAtPosition([4, 4], '}', ["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", () => { + editor.setPhpText("$a = new class{ /* stuff */ };"); + + expect(editor).toHaveScopesAtPosition([1, 5], 'new', ['source.php', "meta.class.php", "keyword.other.new.php"]); + expect(editor).toHaveScopesAtPosition([1, 8], ' ', ['source.php', "meta.class.php"]); + expect(editor).toHaveScopesAtPosition([1, 9], 'class', ['source.php', "meta.class.php", "storage.type.class.php"]); + expect(editor).toHaveScopesAtPosition([1, 14], '{', ["source.php", "meta.class.php", "punctuation.definition.class.begin.bracket.curly.php", + // FIXME following scopes differ from TM + "meta.class.body.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 15], ' ', ['source.php', "meta.class.php", "meta.class.body.php"]); + expect(editor).toHaveScopesAtPosition([1, 16], '/*', ["source.php", "meta.class.php", "meta.class.body.php", "comment.block.php", + // FIXME following scopes differ from TM + // "punctuation.definition.comment.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 19], 'stuff', ["source.php", "meta.class.php", "meta.class.body.php", "comment.block.php"]); + expect(editor).toHaveScopesAtPosition([1, 25], '*/', ["source.php", "meta.class.php", "meta.class.body.php", "comment.block.php", + // FIXME following scopes differ from TM + // "punctuation.definition.comment.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 27], ' ', ['source.php', "meta.class.php", "meta.class.body.php"]); + expect(editor).toHaveScopesAtPosition([1, 28], '}', ["source.php", "meta.class.php", "punctuation.definition.class.end.bracket.curly.php", + // FIXME following scopes differ from TM + "meta.class.body.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 29], ';', ['source.php', "punctuation.terminator.expression.php"]); + }); + + it("tokenizes inheritance correctly", () => { + editor.setPhpText( + "$a = new class extends Test implements ITest { /* stuff */ };" + ); + + expect(editor).toHaveScopesAtPosition([1, 5], 'new', ['source.php', "meta.class.php", "keyword.other.new.php"]); + expect(editor).toHaveScopesAtPosition([1, 9], 'class', ['source.php', "meta.class.php", "storage.type.class.php"]); + expect(editor).toHaveScopesAtPosition([1, 15], 'extends', ['source.php', "meta.class.php", "storage.modifier.extends.php"]); + expect(editor).toHaveScopesAtPosition([1, 23], 'Test', ["source.php", "meta.class.php", "entity.other.inherited-class.php", + // FIXME following scopes differ from TM + // "meta.other.inherited-class.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 28], 'implements', ['source.php', "meta.class.php", "storage.modifier.implements.php"]); + expect(editor).toHaveScopesAtPosition([1, 39], 'ITest', ["source.php", "meta.class.php", + // FIXME following scopes differ from TM + 'entity.name.type.interface.php' + // "meta.other.inherited-class.php", + // "entity.other.inherited-class.php" + ]); + }); + }); + }); + + describe("interfaces", () => { + it("should tokenize multiple inherited interfaces correctly", () => { + editor.setPhpText("interface Superman extends Bird, Plane {}"); + + expect(editor).toHaveScopesAtPosition([1, 0], 'interface', ['source.php', "meta.interface.php", "storage.type.interface.php"]); + expect(editor).toHaveScopesAtPosition([1, 9], ' ', ['source.php', "meta.interface.php"]); + expect(editor).toHaveScopesAtPosition([1, 10], 'Superman', ['source.php', "meta.interface.php", "entity.name.type.interface.php"]); + expect(editor).toHaveScopesAtPosition([1, 18], ' ', ['source.php', "meta.interface.php"]); + expect(editor).toHaveScopesAtPosition([1, 19], 'extends', ['source.php', "meta.interface.php", "storage.modifier.extends.php"]); + expect(editor).toHaveScopesAtPosition([1, 26], ' ', ['source.php', "meta.interface.php"]); + expect(editor).toHaveScopesAtPosition([1, 27], 'Bird', ["source.php", "meta.interface.php", "entity.other.inherited-class.php"]); + expect(editor).toHaveScopesAtPosition([1, 31], ',', ["source.php", "meta.interface.php", "punctuation.separator.classes.php"]); + expect(editor).toHaveScopesAtPosition([1, 32], ' ', ['source.php', "meta.interface.php"]); + expect(editor).toHaveScopesAtPosition([1, 33], 'Plane', ["source.php", "meta.interface.php", "entity.other.inherited-class.php"]); + expect(editor).toHaveScopesAtPosition([1, 38], ' ', ['source.php', "meta.interface.php"]); + expect(editor).toHaveScopesAtPosition([1, 39], '{', ["source.php", "punctuation.definition.begin.bracket.curly.php", + // FIXME following scopes differ from TM + "meta.interface.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 40], '}', ["source.php", "punctuation.definition.end.bracket.curly.php", + // FIXME following scopes differ from TM + "meta.interface.php" + ]); + }); + + it("should tokenize methods in interface correctly", () => { + editor.setPhpText(` + interface Test { + public function testMethod(); + public function __toString(); + } + `); + + expect(editor).toHaveScopesAtPosition([1, 0], 'interface', ['source.php', "meta.interface.php", "storage.type.interface.php"]); + expect(editor).toHaveScopesAtPosition([1, 10], 'Test', ['source.php', "meta.interface.php", "entity.name.type.interface.php"]); + expect(editor).toHaveScopesAtPosition([1, 15], '{', ["source.php", "punctuation.definition.begin.bracket.curly.php", + // FIXME following scopes differ from TM + "meta.interface.php" + ]); + expect(editor).toHaveScopesAtPosition([2, 2], 'public', ["source.php", "meta.function.php", "storage.modifier.php", + // FIXME following scopes differ from TM + "meta.interface.php" + ]); + expect(editor).toHaveScopesAtPosition([2, 9], 'function', ["source.php", "meta.function.php", "storage.type.function.php", + // FIXME following scopes differ from TM + "meta.interface.php" + ]); + expect(editor).toHaveScopesAtPosition([2, 18], 'testMethod', ["source.php", "meta.function.php", "entity.name.function.php", + // FIXME following scopes differ from TM + "meta.interface.php" + ]); + expect(editor).toHaveScopesAtPosition([2, 28], '(', ["source.php", "meta.function.php", "punctuation.definition.parameters.begin.bracket.round.php", + // FIXME following scopes differ from TM + "meta.function.parameters.php", + "meta.interface.php" + ]); + expect(editor).toHaveScopesAtPosition([2, 29], ')', ["source.php", "meta.function.php", "punctuation.definition.parameters.end.bracket.round.php", + // FIXME following scopes differ from TM + "meta.function.parameters.php", + "meta.interface.php" + ]); + expect(editor).toHaveScopesAtPosition([2, 30], ';', ["source.php", "punctuation.terminator.expression.php", + // FIXME following scopes differ from TM + "meta.function.php", + "meta.interface.php" + ]); + expect(editor).toHaveScopesAtPosition([3, 2], 'public', ["source.php", "meta.function.php", "storage.modifier.php", + // FIXME following scopes differ from TM + "meta.interface.php" + ]); + expect(editor).toHaveScopesAtPosition([3, 9], 'function', ["source.php", "meta.function.php", "storage.type.function.php", + // FIXME following scopes differ from TM + "meta.interface.php" + ]); + expect(editor).toHaveScopesAtPosition([3, 18], '__toString', ["source.php", "meta.function.php", "support.function.magic.php", + // FIXME following scopes differ from TM + "meta.function.php", + "meta.interface.php" + ]); + expect(editor).toHaveScopesAtPosition([3, 28], '(', ["source.php", "meta.function.php", "punctuation.definition.parameters.begin.bracket.round.php", + // FIXME following scopes differ from TM + "meta.function.parameters.php", + "meta.interface.php" + ]); + expect(editor).toHaveScopesAtPosition([3, 29], ')', ["source.php", "meta.function.php", "punctuation.definition.parameters.end.bracket.round.php", + // FIXME following scopes differ from TM + "meta.interface.php", + "meta.function.parameters.php" + ]); + expect(editor).toHaveScopesAtPosition([3, 30], ';', ["source.php", "punctuation.terminator.expression.php", + // FIXME following scopes differ from TM + "meta.function.php", + "meta.interface.php" + ]); + expect(editor).toHaveScopesAtPosition([4, 0], '}', ["source.php", "punctuation.definition.end.bracket.curly.php", + // FIXME following scopes differ from TM + "meta.interface.php" + ]); + }); + }); + + describe("traits", () => { + it("should tokenize trait correctly", () => { + editor.setPhpText("trait Test {}"); + + expect(editor).toHaveScopesAtPosition([1, 0], 'trait', ['source.php', "meta.trait.php", "storage.type.trait.php"]); + expect(editor).toHaveScopesAtPosition([1, 5], ' ', ['source.php', "meta.trait.php"]); + expect(editor).toHaveScopesAtPosition([1, 6], 'Test', ['source.php', "meta.trait.php", "entity.name.type.trait.php"]); + expect(editor).toHaveScopesAtPosition([1, 10], ' ', ['source.php', "meta.trait.php"]); + expect(editor).toHaveScopesAtPosition([1, 11], '{', ["source.php", "punctuation.definition.begin.bracket.curly.php", + // FIXME following scopes differ from TM + "meta.trait.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 12], '}', ["source.php", "punctuation.definition.end.bracket.curly.php", + // FIXME following scopes differ from TM + "meta.trait.php" + ]); + }); + }); + + describe("functions", () => { + it("tokenizes functions with no arguments", async () => { + editor.setPhpText("function test() {}"); + + expect(editor).toHaveScopesAtPosition([1, 0], 'function', ['source.php', "meta.function.php", "storage.type.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 8], ' ', ['source.php', "meta.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 9], 'test', ['source.php', "meta.function.php", "entity.name.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 13], '(', ["source.php", "meta.function.php", "punctuation.definition.parameters.begin.bracket.round.php", + // FIXME following scopes differ from TM + "meta.function.parameters.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 14], ')', ["source.php", "meta.function.php", "punctuation.definition.parameters.end.bracket.round.php", + // FIXME following scopes differ from TM + "meta.function.parameters.php" + ]); + + // Should NOT be tokenized as an actual function + editor.setPhpText("function_test() {}"); + await nextHighlightingUpdate(editor) + + expect(editor).toHaveScopesAtPosition([1, 0], 'function_test', ['source.php', "meta.function-call.php", "entity.name.function.php"]); + }); + + it("tokenizes default array type with old array value", () => { + editor.setPhpText("function array_test(array $value = array()) {}"); + + expect(editor).toHaveScopesAtPosition([1, 0], 'function', ['source.php', "meta.function.php", "storage.type.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 8], ' ', ['source.php', "meta.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 9], 'array_test', ['source.php', "meta.function.php", "entity.name.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 19], '(', ["source.php", "meta.function.php", "punctuation.definition.parameters.begin.bracket.round.php", + // FIXME following scopes differ from TM + "meta.function.parameters.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 20], 'array', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", "keyword.other.type.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 25], ' ', ["source.php", "meta.function.php", "meta.function.parameters.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + "meta.function.parameter.default.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 26], '$', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", "variable.other.php", "punctuation.definition.variable.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 27], 'value', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", "variable.other.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 32], ' ', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 33], '=', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", "keyword.operator.assignment.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 34], ' ', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 35], 'array', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", "meta.array.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php", + // "support.function.construct.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 40], '(', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", "meta.array.php", "punctuation.definition.array.begin.bracket.round.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 41], ')', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", "meta.array.php", "punctuation.definition.array.end.bracket.round.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 42], ')', ["source.php", "meta.function.php", "punctuation.definition.parameters.end.bracket.round.php", + // FIXME following scopes differ from TM + "meta.function.parameters.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 43], ' ', ["source.php", + // FIXME following scopes differ from TM + "meta.function.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 44], '{', ["source.php", "punctuation.definition.begin.bracket.curly.php", + // FIXME following scopes differ from TM + "meta.function.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 45], '}', ["source.php", "punctuation.definition.end.bracket.curly.php", + // FIXME following scopes differ from TM + "meta.function.php" + ]); + }); + + it("tokenizes variadic arguments", () => { + editor.setPhpText("function test(...$value) {}"); + + expect(editor).toHaveScopesAtPosition([1, 14], '...', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.variadic.php", "keyword.operator.variadic.php", + // FIXME following scopes differ from TM + // "variable.other.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 17], '$', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.variadic.php", "variable.other.php", "punctuation.definition.variable.php"]); + expect(editor).toHaveScopesAtPosition([1, 18], 'value', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.variadic.php", "variable.other.php"]); + }); + + it("tokenizes variadic arguments and typehinted class name", () => { + editor.setPhpText("function test(class_name ...$value) {}"); + + expect(editor).toHaveScopesAtPosition([1, 14], 'class_name', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.variadic.php", "support.class.php"]); + expect(editor).toHaveScopesAtPosition([1, 25], '...', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.variadic.php", "keyword.operator.variadic.php", + // FIXME following scopes differ from TM + // "variable.other.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 28], '$', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.variadic.php", "variable.other.php", "punctuation.definition.variable.php"]); + expect(editor).toHaveScopesAtPosition([1, 29], 'value', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.variadic.php", "variable.other.php"]); + }); + + it("tokenizes nullable typehints", async () => { + editor.setPhpText("function test(?class_name $value) {}"); + + expect(editor).toHaveScopesAtPosition([1, 14], '?', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.no-default.php", "keyword.operator.nullable-type.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 15], 'class_name', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.no-default.php", "support.class.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 26], '$', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.no-default.php", "variable.other.php", "punctuation.definition.variable.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 27], 'value', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.no-default.php", "variable.other.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + + editor.setPhpText("function test(? class_name $value) {}"); + await nextHighlightingUpdate(editor) + + expect(editor).toHaveScopesAtPosition([1, 14], '?', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.no-default.php", "keyword.operator.nullable-type.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 15], ' ', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.no-default.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 16], 'class_name', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.no-default.php", "support.class.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 27], '$', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.no-default.php", "variable.other.php", "punctuation.definition.variable.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 28], 'value', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.no-default.php", "variable.other.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + }); + + it("tokenizes namespaced and typehinted class names", async () => { + editor.setPhpText("function test(\\class_name $value) {}"); + + expect(editor).toHaveScopesAtPosition([1, 14], '\\', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.no-default.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php", + // "punctuation.separator.inheritance.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 15], 'class_name', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.no-default.php", "support.class.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 26], '$', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.no-default.php", "variable.other.php", "punctuation.definition.variable.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + + editor.setPhpText("function test(a\\class_name $value) {}"); + await nextHighlightingUpdate(editor) + + expect(editor).toHaveScopesAtPosition([1, 14], 'a', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.no-default.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 15], '\\', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.no-default.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php", + // "punctuation.separator.inheritance.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 16], 'class_name', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.no-default.php", "support.class.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 27], '$', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.no-default.php", "variable.other.php", "punctuation.definition.variable.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + + editor.setPhpText("function test(a\\b\\class_name $value) {}"); + await nextHighlightingUpdate(editor) + + expect(editor).toHaveScopesAtPosition([1, 14], 'a', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.no-default.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 15], '\\', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.no-default.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php", + // "punctuation.separator.inheritance.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 16], 'b', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.no-default.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 17], '\\', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.no-default.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php", + // "punctuation.separator.inheritance.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 18], 'class_name', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.no-default.php", "support.class.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 29], '$', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.no-default.php", "variable.other.php", "punctuation.definition.variable.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + + editor.setPhpText("function test(\\a\\b\\class_name $value) {}"); + await nextHighlightingUpdate(editor) + + expect(editor).toHaveScopesAtPosition([1, 14], '\\', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.no-default.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php", + // "punctuation.separator.inheritance.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 15], 'a', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.no-default.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 16], '\\', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.no-default.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php", + // "punctuation.separator.inheritance.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 17], 'b', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.no-default.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 18], '\\', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.no-default.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php", + // "punctuation.separator.inheritance.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 19], 'class_name', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.no-default.php", "support.class.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 30], '$', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.no-default.php", "variable.other.php", "punctuation.definition.variable.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + }); + + it("tokenizes default array type with short array value", () => { + editor.setPhpText("function array_test(array $value = []) {}"); + + expect(editor).toHaveScopesAtPosition([1, 0], 'function', ['source.php', "meta.function.php", "storage.type.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 8], ' ', ['source.php', "meta.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 9], 'array_test', ['source.php', "meta.function.php", "entity.name.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 19], '(', ["source.php", "meta.function.php", "punctuation.definition.parameters.begin.bracket.round.php", + // FIXME following scopes differ from TM + "meta.function.parameters.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 20], 'array', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", "keyword.other.type.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 25], ' ', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 26], '$', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", "variable.other.php", "punctuation.definition.variable.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 27], 'value', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", "variable.other.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 32], ' ', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 33], '=', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", "keyword.operator.assignment.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 34], ' ', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 35], '[', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", "punctuation.section.array.begin.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php", + "meta.array.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 36], ']', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", "punctuation.section.array.end.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php", + "meta.function.parameters.php", + "meta.array.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 37], ')', ["source.php", "meta.function.php", "punctuation.definition.parameters.end.bracket.round.php", + // FIXME following scopes differ from TM + "meta.function.parameters.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 38], ' ', ["source.php", + // FIXME following scopes differ from TM + "meta.function.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 39], '{', ["source.php", "punctuation.definition.begin.bracket.curly.php", // FIXME following scopes differ from TM + "meta.function.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 40], '}', ["source.php", "punctuation.definition.end.bracket.curly.php", + // FIXME following scopes differ from TM + "meta.function.php" + ]); + }); + + it("tokenizes a non-empty array", () => { + editor.setPhpText( + "function not_empty_array_test(array $value = [1,2,'3']) {}" + ); + + expect(editor).toHaveScopesAtPosition([1, 45], '[', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", "punctuation.section.array.begin.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php", + "meta.array.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 46], '1', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", "constant.numeric.decimal.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php", + "meta.array.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 47], ',', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", "punctuation.separator.delimiter.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php", + "meta.array.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 48], '2', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", "constant.numeric.decimal.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php", + "meta.array.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 49], ',', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", "punctuation.separator.delimiter.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php", + "meta.array.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 50], '\'', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", "string.quoted.single.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php", + // "punctuation.definition.string.begin.php", + "meta.array.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 51], '3', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", "string.quoted.single.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php", + "meta.array.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 52], '\'', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", "string.quoted.single.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php", + // "punctuation.definition.string.end.php", + "meta.array.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 53], ']', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", "punctuation.section.array.end.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php", + "meta.array.php" + ]); + }); + + it("tokenizes default value with non-lowercase array type hinting", () => { + editor.setPhpText("function array_test(Array $value = []) {}"); + + expect(editor).toHaveScopesAtPosition([1, 0], 'function', ['source.php', "meta.function.php", "storage.type.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 8], ' ', ['source.php', "meta.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 9], 'array_test', ['source.php', "meta.function.php", "entity.name.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 19], '(', ["source.php", "meta.function.php", "punctuation.definition.parameters.begin.bracket.round.php", + // FIXME following scopes differ from TM + "meta.function.parameters.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 20], 'Array', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", "keyword.other.type.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 25], ' ', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 26], '$', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", "variable.other.php", "punctuation.definition.variable.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 27], 'value', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", "variable.other.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 32], ' ', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 33], '=', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", "keyword.operator.assignment.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 34], ' ', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 35], '[', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", "punctuation.section.array.begin.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php", + "meta.array.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 36], ']', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", "punctuation.section.array.end.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php", + "meta.array.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 37], ')', ["source.php", "meta.function.php", "punctuation.definition.parameters.end.bracket.round.php", + // FIXME following scopes differ from TM + "meta.function.parameters.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 38], ' ', ["source.php", + // FIXME following scopes differ from TM + "meta.function.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 39], '{', ["source.php", "punctuation.definition.begin.bracket.curly.php", + // FIXME following scopes differ from TM + "meta.function.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 40], '}', ["source.php", "punctuation.definition.end.bracket.curly.php", + // FIXME following scopes differ from TM + "meta.function.php" + ]); + }); + + it("tokenizes multiple typehinted arguments with default values", () => { + editor.setPhpText( + "function test(string $subject = 'no subject', string $body = null) {}" + ); + + expect(editor).toHaveScopesAtPosition([1, 0], 'function', ['source.php', "meta.function.php", "storage.type.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 8], ' ', ['source.php', "meta.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 9], 'test', ['source.php', "meta.function.php", "entity.name.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 13], '(', ["source.php", "meta.function.php", "punctuation.definition.parameters.begin.bracket.round.php", + // FIXME following scopes differ from TM + "meta.function.parameters.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 14], 'string', ["source.php", "meta.function.php", "meta.function.parameters.php", "keyword.other.type.php", "meta.function.parameter.default.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 21], '$', ["source.php", "meta.function.php", "meta.function.parameters.php", "variable.other.php", "punctuation.definition.variable.php", "meta.function.parameter.default.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 22], 'subject', ["source.php", "meta.function.php", "meta.function.parameters.php", "variable.other.php", "meta.function.parameter.default.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 29], ' ', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 30], '=', ["source.php", "meta.function.php", "meta.function.parameters.php", "keyword.operator.assignment.php", "meta.function.parameter.default.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 31], ' ', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 32], "'", ["source.php", "meta.function.php", "meta.function.parameters.php", "string.quoted.single.php", "meta.function.parameter.default.php", + // FIXME following scopes differ from TM + // "punctuation.definition.string.begin.php" + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 33], 'no subject', ["source.php", "meta.function.php", "meta.function.parameters.php", "string.quoted.single.php", "meta.function.parameter.default.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 43], "'", ["source.php", "meta.function.php", "meta.function.parameters.php", "string.quoted.single.php", "meta.function.parameter.default.php", + // FIXME following scopes differ from TM + // "punctuation.definition.string.end.php", + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 44], ',', ["source.php", "meta.function.php", "meta.function.parameters.php", "punctuation.separator.delimiter.php"]); + expect(editor).toHaveScopesAtPosition([1, 45], ' ', ['source.php', "meta.function.php", "meta.function.parameters.php"]); + expect(editor).toHaveScopesAtPosition([1, 46], 'string', ["source.php", "meta.function.php", "meta.function.parameters.php", "keyword.other.type.php", "meta.function.parameter.default.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 53], '$', ["source.php", "meta.function.php", "meta.function.parameters.php", "variable.other.php", "punctuation.definition.variable.php", "meta.function.parameter.default.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 54], 'body', ["source.php", "meta.function.php", "meta.function.parameters.php", "variable.other.php", "meta.function.parameter.default.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 59], '=', ["source.php", "meta.function.php", "meta.function.parameters.php", "keyword.operator.assignment.php", "meta.function.parameter.default.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 60], ' ', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.default.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 61], 'null', ["source.php", "meta.function.php", "meta.function.parameters.php", "constant.language.php", "meta.function.parameter.default.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 65], ')', ["source.php", "meta.function.php", "punctuation.definition.parameters.end.bracket.round.php", + // FIXME following scopes differ from TM + "meta.function.parameters.php" + ]); + }); + + it("tokenizes union types in function parameters", async () => { + editor.setPhpText('function test(int|false $a){}'); + + expect(editor).toHaveScopesAtPosition([1, 0], 'function', ['source.php', 'meta.function.php', 'storage.type.function.php']) + expect(editor).toHaveScopesAtPosition([1, 9], 'test', ['source.php', 'meta.function.php', 'entity.name.function.php']) + expect(editor).toHaveScopesAtPosition([1, 13], '(', ['source.php', 'meta.function.php', 'punctuation.definition.parameters.begin.bracket.round.php', + // FIXME following scopes differ from TM + 'meta.function.parameters.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 14], 'int', ['source.php', 'meta.function.php', 'meta.function.parameters.php', 'keyword.other.type.php', + // FIXME following scopes differ from TM + // 'meta.function.parameter.typehinted.php' + 'meta.function.parameter.no-default.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 17], '|', ['source.php', 'meta.function.php', 'meta.function.parameters.php', 'punctuation.separator.delimiter.php', + // FIXME following scopes differ from TM + // 'meta.function.parameter.typehinted.php' + 'meta.function.parameter.no-default.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 18], 'false', ['source.php', 'meta.function.php', 'meta.function.parameters.php', 'keyword.other.type.php', + // FIXME following scopes differ from TM + // 'meta.function.parameter.typehinted.php' + 'meta.function.parameter.no-default.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 23], ' ', ['source.php', 'meta.function.php', 'meta.function.parameters.php', + // FIXME following scopes differ from TM + // 'meta.function.parameter.typehinted.php' + 'meta.function.parameter.no-default.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 24], '$', ['source.php', 'meta.function.php', 'meta.function.parameters.php', 'variable.other.php', 'punctuation.definition.variable.php', + // FIXME following scopes differ from TM + // 'meta.function.parameter.typehinted.php' + 'meta.function.parameter.no-default.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 25], 'a', ['source.php', 'meta.function.php', 'meta.function.parameters.php', 'variable.other.php', + // FIXME following scopes differ from TM + // 'meta.function.parameter.typehinted.php' + 'meta.function.parameter.no-default.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 26], ')', ['source.php', 'meta.function.php', 'punctuation.definition.parameters.end.bracket.round.php', + // FIXME following scopes differ from TM + 'meta.function.parameters.php' + ]) + + editor.setPhpText('function test(\\Abc\\ClassA | mixed $a){}'); + await nextHighlightingUpdate(editor) + + expect(editor).toHaveScopesAtPosition([1, 0], 'function', ['source.php', 'meta.function.php', 'storage.type.function.php']) + expect(editor).toHaveScopesAtPosition([1, 9], 'test', ['source.php', 'meta.function.php', 'entity.name.function.php']) + expect(editor).toHaveScopesAtPosition([1, 13], '(', ['source.php', 'meta.function.php', 'punctuation.definition.parameters.begin.bracket.round.php', + // FIXME following scopes differ from TM + 'meta.function.parameters.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 14], '\\', ['source.php', 'meta.function.php', 'meta.function.parameters.php', 'support.other.namespace.php', + // FIXME following scopes differ from TM + // 'punctuation.separator.inheritance.php', + // 'meta.function.parameter.typehinted.php' + 'meta.function.parameter.no-default.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 15], 'Abc', ['source.php', 'meta.function.php', 'meta.function.parameters.php', 'support.other.namespace.php', + // FIXME following scopes differ from TM + // 'meta.function.parameter.typehinted.php' + 'meta.function.parameter.no-default.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 18], '\\', ['source.php', 'meta.function.php', 'meta.function.parameters.php', 'support.other.namespace.php', + // FIXME following scopes differ from TM + // 'punctuation.separator.inheritance.php', + // 'meta.function.parameter.typehinted.php' + 'meta.function.parameter.no-default.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 19], 'ClassA', ['source.php', 'meta.function.php', 'meta.function.parameters.php', 'support.class.php', + // FIXME following scopes differ from TM + // 'meta.function.parameter.typehinted.php' + 'meta.function.parameter.no-default.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 25], ' ', ['source.php', 'meta.function.php', 'meta.function.parameters.php', + // FIXME following scopes differ from TM + // 'meta.function.parameter.typehinted.php' + 'meta.function.parameter.no-default.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 26], '|', ['source.php', 'meta.function.php', 'meta.function.parameters.php', 'punctuation.separator.delimiter.php', + // FIXME following scopes differ from TM + // 'meta.function.parameter.typehinted.php' + 'meta.function.parameter.no-default.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 27], ' ', ['source.php', 'meta.function.php', 'meta.function.parameters.php', + // FIXME following scopes differ from TM + // 'meta.function.parameter.typehinted.php' + 'meta.function.parameter.no-default.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 28], 'mixed', ['source.php', 'meta.function.php', 'meta.function.parameters.php', 'keyword.other.type.php', + // FIXME following scopes differ from TM + // 'meta.function.parameter.typehinted.php' + 'meta.function.parameter.no-default.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 33], ' ', ['source.php', 'meta.function.php', 'meta.function.parameters.php', + // FIXME following scopes differ from TM + // 'meta.function.parameter.typehinted.php' + 'meta.function.parameter.no-default.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 34], '$', ['source.php', 'meta.function.php', 'meta.function.parameters.php', 'variable.other.php', 'punctuation.definition.variable.php', + // FIXME following scopes differ from TM + // 'meta.function.parameter.typehinted.php' + 'meta.function.parameter.no-default.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 35], 'a', ['source.php', 'meta.function.php', 'meta.function.parameters.php', 'variable.other.php', + // FIXME following scopes differ from TM + // 'meta.function.parameter.typehinted.php' + 'meta.function.parameter.no-default.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 36], ')', ['source.php', 'meta.function.php', 'punctuation.definition.parameters.end.bracket.round.php', + // FIXME following scopes differ from TM + 'meta.function.parameters.php' + ]) + }) + + // FIXME TS-php doesn't support intersection types yet + xit('tokenizes intersection types in function parameters', () => { + editor.setPhpText('function test(FooInterface&BarInterface $foobar){}') + + expect(editor).toHaveScopesAtPosition([1, 0], 'function', ['source.php', 'meta.function.php', 'storage.type.function.php']) + expect(editor).toHaveScopesAtPosition([1, 9], 'test', ['source.php', 'meta.function.php', 'entity.name.function.php']) + expect(editor).toHaveScopesAtPosition([1, 13], '(', ['source.php', 'meta.function.php', 'punctuation.definition.parameters.begin.bracket.round.php', + // FIXME following scopes differ from TM + 'meta.function.parameters.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 14], 'FooInterface', ['source.php', 'meta.function.php', 'meta.function.parameters.php', 'support.class.php', + // FIXME following scopes differ from TM + // 'meta.function.parameter.typehinted.php' + 'meta.function.parameter.no-default.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 26], '&', ['source.php', 'meta.function.php', 'meta.function.parameters.php', 'punctuation.separator.delimiter.php', + // FIXME following scopes differ from TM + // 'meta.function.parameter.typehinted.php' + 'meta.function.parameter.no-default.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 27], 'BarInterface', ['source.php', 'meta.function.php', 'meta.function.parameters.php', 'support.class.php', + // FIXME following scopes differ from TM + // 'meta.function.parameter.typehinted.php' + 'meta.function.parameter.no-default.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 40], '$', ['source.php', 'meta.function.php', 'meta.function.parameters.php', 'variable.other.php', 'punctuation.definition.variable.php', + // FIXME following scopes differ from TM + // 'meta.function.parameter.typehinted.php' + 'meta.function.parameter.no-default.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 41], 'foobar', ['source.php', 'meta.function.php', 'meta.function.parameters.php', 'variable.other.php', + // FIXME following scopes differ from TM + // 'meta.function.parameter.typehinted.php' + 'meta.function.parameter.no-default.php' + ]) + }) + + it("tokenizes trailing comma in function parameters", () => { + editor.setPhpText("function abc($a, $b,){}"); + + expect(editor).toHaveScopesAtPosition([1, 0], 'function', ['source.php', "meta.function.php", "storage.type.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 9], 'abc', ['source.php', "meta.function.php", "entity.name.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 12], '(', ["source.php", "meta.function.php", "punctuation.definition.parameters.begin.bracket.round.php", + // FIXME following scopes differ from TM + "meta.function.parameters.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 13], '$', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.no-default.php", "variable.other.php", "punctuation.definition.variable.php"]); + expect(editor).toHaveScopesAtPosition([1, 14], 'a', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.no-default.php", "variable.other.php"]); + expect(editor).toHaveScopesAtPosition([1, 15], ',', ["source.php", "meta.function.php", "meta.function.parameters.php", "punctuation.separator.delimiter.php"]); + expect(editor).toHaveScopesAtPosition([1, 17], '$', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.no-default.php", "variable.other.php", "punctuation.definition.variable.php"]); + expect(editor).toHaveScopesAtPosition([1, 18], 'b', ["source.php", "meta.function.php", "meta.function.parameters.php", "meta.function.parameter.no-default.php", "variable.other.php"]); + expect(editor).toHaveScopesAtPosition([1, 19], ',', ["source.php", "meta.function.php", "meta.function.parameters.php", "punctuation.separator.delimiter.php"]); + expect(editor).toHaveScopesAtPosition([1, 20], ')', ["source.php", "meta.function.php", "punctuation.definition.parameters.end.bracket.round.php", + // FIXME following scopes differ from TM + "meta.function.parameters.php" + ]); + }); + + it("tokenizes return values", () => { + editor.setPhpText("function test() : Client {}"); + + expect(editor).toHaveScopesAtPosition([1, 0], 'function', ['source.php', "meta.function.php", "storage.type.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 8], ' ', ['source.php', "meta.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 9], 'test', ['source.php', "meta.function.php", "entity.name.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 13], '(', ["source.php", "meta.function.php", "punctuation.definition.parameters.begin.bracket.round.php", + // FIXME following scopes differ from TM + "meta.function.parameters.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 14], ')', ["source.php", "meta.function.php", "punctuation.definition.parameters.end.bracket.round.php", + // FIXME following scopes differ from TM + "meta.function.parameters.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 15], ' ', ['source.php', "meta.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 16], ':', ['source.php', "meta.function.php", "keyword.operator.return-value.php"]); + expect(editor).toHaveScopesAtPosition([1, 17], ' ', ['source.php', "meta.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 18], 'Client', ['source.php', "meta.function.php", "support.class.php"]); + // ' ', + expect(editor).toHaveScopesAtPosition([1, 24], ["source.php", + // FIXME following scopes differ from TM + "meta.function.php" + ]); + }); + + it("tokenizes nullable return values", async () => { + editor.setPhpText("function test() : ?Client {}"); + + expect(editor).toHaveScopesAtPosition([1, 16], ':', ['source.php', "meta.function.php", "keyword.operator.return-value.php"]); + expect(editor).toHaveScopesAtPosition([1, 17], ' ', ['source.php', "meta.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 18], '?', ["source.php", "meta.function.php", "keyword.operator.nullable-type.php"]); + expect(editor).toHaveScopesAtPosition([1, 19], 'Client', ['source.php', "meta.function.php", "support.class.php"]); + + editor.setPhpText("function test() : ? Client {}"); + await nextHighlightingUpdate(editor) + + expect(editor).toHaveScopesAtPosition([1, 16], ':', ['source.php', "meta.function.php", "keyword.operator.return-value.php"]); + expect(editor).toHaveScopesAtPosition([1, 17], ' ', ['source.php', "meta.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 18], '?', ["source.php", "meta.function.php", "keyword.operator.nullable-type.php"]); + expect(editor).toHaveScopesAtPosition([1, 19], ' ', ['source.php', "meta.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 22], 'Client', ['source.php', "meta.function.php", "support.class.php"]); + }); + + it('tokenizes union return types', () => { + editor.setPhpText('function test() : \\ClassB | null {}') + + expect(editor).toHaveScopesAtPosition([1, 16], ':', ['source.php', 'meta.function.php', 'keyword.operator.return-value.php']) + expect(editor).toHaveScopesAtPosition([1, 18], '\\', ['source.php', 'meta.function.php', 'support.other.namespace.php', + // FIXME following scopes differ from TM + // 'punctuation.separator.inheritance.php', + ]) + expect(editor).toHaveScopesAtPosition([1, 19], 'ClassB', ['source.php', 'meta.function.php', 'support.class.php']) + expect(editor).toHaveScopesAtPosition([1, 25], ' ', ['source.php', 'meta.function.php']) + expect(editor).toHaveScopesAtPosition([1, 26], '|', ['source.php', 'meta.function.php', 'punctuation.separator.delimiter.php']) + expect(editor).toHaveScopesAtPosition([1, 27], ' ', ['source.php', 'meta.function.php']) + expect(editor).toHaveScopesAtPosition([1, 28], 'null', ['source.php', 'meta.function.php', 'keyword.other.type.php'] ) + }) + + // FIXME TS-php doesn't support intersection types yet + xit('tokenizes intersection return types', () => { + editor.setPhpText('function foobar() : FooInterface & BarInterface {}') + + expect(editor).toHaveScopesAtPosition([1, 0], 'function', ['source.php', 'meta.function.php', 'storage.type.function.php']) + expect(editor).toHaveScopesAtPosition([1, 9], 'foobar', ['source.php', 'meta.function.php', 'entity.name.function.php']) + expect(editor).toHaveScopesAtPosition([1, 18], ':', ['source.php', 'meta.function.php', 'keyword.operator.return-value.php']) + expect(editor).toHaveScopesAtPosition([1, 20], 'FooInterface', ['source.php', 'meta.function.php', 'support.class.php']) + expect(editor).toHaveScopesAtPosition([1, 33], '&', ['source.php', 'meta.function.php', 'punctuation.separator.delimiter.php']) + expect(editor).toHaveScopesAtPosition([1, 35], 'BarInterface', ['source.php', 'meta.function.php', 'support.class.php']) + }) + + // TS handles parsing of function names so that we don't have to + // it('tokenizes function names with characters other than letters or numbers', () => {}) + + it("tokenizes function returning reference", () => { + editor.setPhpText("function &test() {}"); + + expect(editor).toHaveScopesAtPosition([1, 0], 'function', ['source.php', "meta.function.php", "storage.type.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 9], '&', ['source.php', "meta.function.php", "storage.modifier.reference.php"]); + expect(editor).toHaveScopesAtPosition([1, 10], 'test', ['source.php', "meta.function.php", "entity.name.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 14], '(', ["source.php", "meta.function.php", "punctuation.definition.parameters.begin.bracket.round.php", + // FIXME following scopes differ from TM + "meta.function.parameters.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 15], ')', ["source.php", "meta.function.php", "punctuation.definition.parameters.end.bracket.round.php", + // FIXME following scopes differ from TM + "meta.function.parameters.php" + ]); + }); + + it("tokenizes yield", () => { + editor.setPhpText("function test() { yield $a; }"); + + expect(editor).toHaveScopesAtPosition([1, 18], 'yield', ["source.php", "keyword.control.yield.php", + // FIXME following scopes differ from TM + "meta.function.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 23], ' ', ["source.php", + // FIXME following scopes differ from TM + "meta.function.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 24], '$', ["source.php", "variable.other.php", "punctuation.definition.variable.php", + // FIXME following scopes differ from TM + "meta.function.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 25], 'a', ["source.php", "variable.other.php", + // FIXME following scopes differ from TM + "meta.function.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 26], ';', ["source.php", "punctuation.terminator.expression.php", + // FIXME following scopes differ from TM + "meta.function.php" + ]); + }); + + it("tokenizes `yield from`", () => { + editor.setPhpText("function test() { yield from $a; }"); + + expect(editor).toHaveScopesAtPosition([1, 18], 'yield from', ["source.php", "keyword.control.yield.php", "meta.function.php" + // FIXME following scopes differ from TM + // 'keyword.control.yield-from.php', + ]); + expect(editor).toHaveScopesAtPosition([1, 24], 'from', ["source.php", "keyword.control.yield-from.php", + // FIXME following scopes differ from TM + "meta.function.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 28], ' ', ["source.php", + // FIXME following scopes differ from TM + "meta.function.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 29], '$', ["source.php", "variable.other.php", "punctuation.definition.variable.php", + // FIXME following scopes differ from TM + "meta.function.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 30], 'a', ["source.php", "variable.other.php", + // FIXME following scopes differ from TM + "meta.function.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 31], ';', ["source.php", "punctuation.terminator.expression.php", + // FIXME following scopes differ from TM + "meta.function.php" + ]); + }); + }); + + describe("function calls", () => { + it("tokenizes function calls with no arguments", async () => { + editor.setPhpText("inverse()"); + + expect(editor).toHaveScopesAtPosition([1, 0], 'inverse', ['source.php', "meta.function-call.php", "entity.name.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 7], '(', ["source.php", "meta.function-call.php", "punctuation.definition.arguments.begin.bracket.round.php"]); + expect(editor).toHaveScopesAtPosition([1, 8], ')', ["source.php", "meta.function-call.php", "punctuation.definition.arguments.end.bracket.round.php"]); + + editor.setPhpText("inverse ()"); + await nextHighlightingUpdate(editor); + + expect(editor).toHaveScopesAtPosition([1, 0], 'inverse', ['source.php', "meta.function-call.php", "entity.name.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 7], ' ', ['source.php', "meta.function-call.php"]); + expect(editor).toHaveScopesAtPosition([1, 8], '(', ["source.php", "meta.function-call.php", "punctuation.definition.arguments.begin.bracket.round.php"]); + expect(editor).toHaveScopesAtPosition([1, 9], ')', ["source.php", "meta.function-call.php", "punctuation.definition.arguments.end.bracket.round.php"]); + }); + + it("tokenizes function calls with arguments", () => { + editor.setPhpText("inverse(5, 'b')"); + + expect(editor).toHaveScopesAtPosition([1, 0], 'inverse', ['source.php', "meta.function-call.php", "entity.name.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 7], '(', ["source.php", "meta.function-call.php", "punctuation.definition.arguments.begin.bracket.round.php"]); + expect(editor).toHaveScopesAtPosition([1, 8], '5', ['source.php', "meta.function-call.php", "constant.numeric.decimal.php"]); + expect(editor).toHaveScopesAtPosition([1, 9], ',', ["source.php", "meta.function-call.php", "punctuation.separator.delimiter.php"]); + expect(editor).toHaveScopesAtPosition([1, 10], ' ', ['source.php', "meta.function-call.php"]); + expect(editor).toHaveScopesAtPosition([1, 11], "'", ["source.php", "meta.function-call.php", "string.quoted.single.php", + // FIXME following scopes differ from TM + // "punctuation.definition.string.begin.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 12], 'b', ['source.php', "meta.function-call.php", "string.quoted.single.php"]); + expect(editor).toHaveScopesAtPosition([1, 13], "'", ["source.php", "meta.function-call.php", "string.quoted.single.php", + // FIXME following scopes differ from TM + // "punctuation.definition.string.end.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 14], ')', ["source.php", "meta.function-call.php", "punctuation.definition.arguments.end.bracket.round.php"]); + }); + + it('tokenizes function calls with named arguments', () => { + editor.setPhpText('doSomething($a ? null : true, b: $b);') + + expect(editor).toHaveScopesAtPosition([1, 0], 'doSomething', ['source.php', 'meta.function-call.php', 'entity.name.function.php']) + + // handle the ternary correctly + expect(editor).toHaveScopesAtPosition([1, 17], 'null', ['source.php', 'meta.function-call.php', 'constant.language.php']) + expect(editor).toHaveScopesAtPosition([1, 22], ':', ['source.php', 'meta.function-call.php', 'keyword.operator.ternary.php']) + + // handle the named argument correctly + expect(editor).toHaveScopesAtPosition([1, 30], 'b', ['source.php', 'meta.function-call.php', 'entity.name.variable.parameter.php']) + expect(editor).toHaveScopesAtPosition([1, 31], ':', ['source.php', 'meta.function-call.php', 'punctuation.separator.colon.php']) + + }) + + it('tokenizes multiline function calls with named arguments', () => { + editor.setPhpText(` + doSomething( + x: $a ? + null : true, + a: $b); + `) + + expect(editor).toHaveScopesAtPosition([1, 0], 'doSomething', ['source.php', 'meta.function-call.php', 'entity.name.function.php']) + + expect(editor).toHaveScopesAtPosition([2, 2], 'x', ['source.php', 'meta.function-call.php', 'entity.name.variable.parameter.php']) + expect(editor).toHaveScopesAtPosition([2, 3], ':', ['source.php', 'meta.function-call.php', 'punctuation.separator.colon.php']) + + // ternary should be still tokenized + expect(editor).toHaveScopesAtPosition([3, 2], 'null', ['source.php', 'meta.function-call.php', 'constant.language.php']) + expect(editor).toHaveScopesAtPosition([3, 7], ':', ['source.php', 'meta.function-call.php', 'keyword.operator.ternary.php']) + + // handle the named argument correctly + expect(editor).toHaveScopesAtPosition([4, 2], 'a', ['source.php', 'meta.function-call.php', 'entity.name.variable.parameter.php']) + expect(editor).toHaveScopesAtPosition([4, 3], ':', ['source.php', 'meta.function-call.php', 'punctuation.separator.colon.php']) + }) + + it("tokenizes trailing comma in parameters of function call", () => { + editor.setPhpText("add(1,2,)"); + + expect(editor).toHaveScopesAtPosition([1, 0], 'add', ['source.php', "meta.function-call.php", "entity.name.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 4], '1', ['source.php', "meta.function-call.php", "constant.numeric.decimal.php"]); + expect(editor).toHaveScopesAtPosition([1, 5], ',', ["source.php", "meta.function-call.php", "punctuation.separator.delimiter.php"]); + expect(editor).toHaveScopesAtPosition([1, 6], '2', ['source.php', "meta.function-call.php", "constant.numeric.decimal.php"]); + expect(editor).toHaveScopesAtPosition([1, 7], ',', ["source.php", "meta.function-call.php", "punctuation.separator.delimiter.php"]); + }); + + it("tokenizes builtin function calls", () => { + editor.setPhpText("echo('Hi!');"); + + expect(editor).toHaveScopesAtPosition([1, 0], 'echo', ["source.php", "meta.function-call.php", "support.function.construct.output.php"]); + expect(editor).toHaveScopesAtPosition([1, 4], '(', ["source.php", "meta.function-call.php", "punctuation.definition.arguments.begin.bracket.round.php"]); + expect(editor).toHaveScopesAtPosition([1, 5], "'", ["source.php", "meta.function-call.php", "string.quoted.single.php", + // FIXME following scopes differ from TM + //'punctuation.definition.string.begin.php' + ]); + expect(editor).toHaveScopesAtPosition([1, 6], 'Hi!', ['source.php', "meta.function-call.php", "string.quoted.single.php"]); + expect(editor).toHaveScopesAtPosition([1, 9], "'", ["source.php", "meta.function-call.php", "string.quoted.single.php", + // FIXME following scopes differ from TM + // 'punctuation.definition.string.end.php' + ]); + expect(editor).toHaveScopesAtPosition([1, 10], ')', ["source.php", "meta.function-call.php", "punctuation.definition.arguments.end.bracket.round.php"]); + }); + + it("tokenizes root-namespaced function calls", () => { + editor.setPhpText("\\test()"); + + expect(editor).toHaveScopesAtPosition([1, 0], '\\', ["source.php", "meta.function-call.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 1], 'test', ['source.php', "meta.function-call.php", "entity.name.function.php"]); + }); + + it("tokenizes user-namespaced function calls", async () => { + editor.setPhpText("hello\\test()"); + + expect(editor).toHaveScopesAtPosition([1, 0], 'hello', ['source.php', "meta.function-call.php", "support.other.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 5], '\\', ["source.php", "meta.function-call.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 6], 'test', ['source.php', "meta.function-call.php", "entity.name.function.php"]); + + editor.setPhpText("one\\two\\test()"); + await nextHighlightingUpdate(editor); + + expect(editor).toHaveScopesAtPosition([1, 0], 'one', ['source.php', "meta.function-call.php", "support.other.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 3], '\\', ["source.php", "meta.function-call.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 4], 'two', ['source.php', "meta.function-call.php", "support.other.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 7], '\\', ["source.php", "meta.function-call.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 8], 'test', ['source.php', "meta.function-call.php", "entity.name.function.php"]); + }); + + it("tokenizes absolutely-namespaced function calls", async () => { + editor.setPhpText("\\hello\\test()"); + + expect(editor).toHaveScopesAtPosition([1, 0], '\\', ["source.php", "meta.function-call.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 1], 'hello', ['source.php', "meta.function-call.php", "support.other.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 6], '\\', ["source.php", "meta.function-call.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 7], 'test', ['source.php', "meta.function-call.php", "entity.name.function.php"]); + + editor.setPhpText("\\one\\two\\test()"); + await nextHighlightingUpdate(editor); + + expect(editor).toHaveScopesAtPosition([1, 0], '\\', ["source.php", "meta.function-call.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 1], 'one', ['source.php', "meta.function-call.php", "support.other.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 4], '\\', ["source.php", "meta.function-call.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 5], 'two', ['source.php', "meta.function-call.php", "support.other.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 8], '\\', ["source.php", "meta.function-call.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 9], 'test', ['source.php', "meta.function-call.php", "entity.name.function.php"]); + }); + + it("does not treat user-namespaced functions as builtins", async () => { + editor.setPhpText("hello\\apc_store()"); + + expect(editor).toHaveScopesAtPosition([1, 6], 'apc_store', ['source.php', "meta.function-call.php", "entity.name.function.php"]); + + editor.setPhpText("\\hello\\apc_store()"); + await nextHighlightingUpdate(editor); + + expect(editor).toHaveScopesAtPosition([1, 7], 'apc_store', ['source.php', "meta.function-call.php", "entity.name.function.php"]); + }); + + it("tokenizes closure calls", () => { + editor.setPhpText("$callback()"); + + expect(editor).toHaveScopesAtPosition([1, 0], '$', ["source.php", "meta.function-call.invoke.php", "variable.other.php", "punctuation.definition.variable.php"]); + expect(editor).toHaveScopesAtPosition([1, 1], 'callback', ['source.php', "meta.function-call.invoke.php", "variable.other.php"]); + expect(editor).toHaveScopesAtPosition([1, 9], '(', ["source.php", "punctuation.definition.arguments.begin.bracket.round.php", "meta.function-call.invoke.php", + // FIXME following scopes differ from TM + // "punctuation.definition.begin.bracket.round.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 10], ')', ["source.php", "punctuation.definition.arguments.end.bracket.round.php", "meta.function-call.invoke.php", + // FIXME following scopes differ from TM + // "punctuation.definition.end.bracket.round.php" + ]); + }); + }); + + it('should tokenize non-function-non-control operations correctly', () => { + editor.setPhpText("echo 'test';"); + + expect(editor).toHaveScopesAtPosition([1, 0], 'echo', ['source.php', 'support.function.construct.output.php']) + expect(editor).toHaveScopesAtPosition([1, 4], ' ', ['source.php']) + expect(editor).toHaveScopesAtPosition([1, 5], '\'', ['source.php', 'string.quoted.single.php', + // FIXME following scopes differ from TM + // 'punctuation.definition.string.begin.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 6], 'test', ['source.php', 'string.quoted.single.php']) + expect(editor).toHaveScopesAtPosition([1, 10], '\'', ['source.php', 'string.quoted.single.php', + // FIXME following scopes differ from TM + // 'punctuation.definition.string.end.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 11], ';', ['source.php', 'punctuation.terminator.expression.php']) + }) + + describe("method calls", () => { + it("tokenizes method calls with no arguments", async () => { + // FIXME the TM test didn't include the leading $ ... which is invalid + // syntax, right? + editor.setPhpText("$obj->method()"); + + expect(editor).toHaveScopesAtPosition([1, 4], '->', ['source.php', "meta.method-call.php", "keyword.operator.class.php"]); + expect(editor).toHaveScopesAtPosition([1, 6], 'method', ['source.php', "meta.method-call.php", "entity.name.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 12], '(', ["source.php", "meta.method-call.php", "punctuation.definition.arguments.begin.bracket.round.php"]); + expect(editor).toHaveScopesAtPosition([1, 13], ')', ["source.php", "meta.method-call.php", "punctuation.definition.arguments.end.bracket.round.php"]); + + editor.setPhpText("$obj-> method ()"); + await nextHighlightingUpdate(editor) + + expect(editor).toHaveScopesAtPosition([1, 4], '->', ['source.php', "meta.method-call.php", "keyword.operator.class.php"]); + expect(editor).toHaveScopesAtPosition([1, 6], ' ', ['source.php', "meta.method-call.php"]); + expect(editor).toHaveScopesAtPosition([1, 7], 'method', ['source.php', "meta.method-call.php", "entity.name.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 13], ' ', ['source.php', "meta.method-call.php"]); + expect(editor).toHaveScopesAtPosition([1, 14], '(', ["source.php", "meta.method-call.php", "punctuation.definition.arguments.begin.bracket.round.php"]); + expect(editor).toHaveScopesAtPosition([1, 15], ')', ["source.php", "meta.method-call.php", "punctuation.definition.arguments.end.bracket.round.php"]); + }); + + it('tokenizes method calls with nullsafe operator', () => { + // FIXME the TM test didn't include the leading $ ... which is invalid + // syntax, right? + editor.setPhpText('$obj?->method()') + + expect(editor).toHaveScopesAtPosition([1, 4], '?->', ['source.php', 'meta.method-call.php', 'keyword.operator.class.php']) + expect(editor).toHaveScopesAtPosition([1, 7], 'method', ['source.php', 'meta.method-call.php', 'entity.name.function.php']) + expect(editor).toHaveScopesAtPosition([1, 13], '(', ['source.php', 'meta.method-call.php', 'punctuation.definition.arguments.begin.bracket.round.php']) + expect(editor).toHaveScopesAtPosition([1, 14], ')', ['source.php', 'meta.method-call.php', 'punctuation.definition.arguments.end.bracket.round.php']) + + }) + + it("tokenizes method calls with arguments", () => { + // FIXME the TM test didn't include the leading $ ... which is invalid + // syntax, right? + editor.setPhpText("$obj->method(5, 'b')"); + + expect(editor).toHaveScopesAtPosition([1, 6], 'method', ['source.php', "meta.method-call.php", "entity.name.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 12], '(', ["source.php", "meta.method-call.php", "punctuation.definition.arguments.begin.bracket.round.php"]); + expect(editor).toHaveScopesAtPosition([1, 13], '5', ['source.php', "meta.method-call.php", "constant.numeric.decimal.php"]); + expect(editor).toHaveScopesAtPosition([1, 14], ',', ["source.php", "meta.method-call.php", "punctuation.separator.delimiter.php"]); + expect(editor).toHaveScopesAtPosition([1, 15], ' ', ['source.php', "meta.method-call.php"]); + expect(editor).toHaveScopesAtPosition([1, 16], "'", ["source.php", "meta.method-call.php", "string.quoted.single.php", + // FIXME following scopes differ from TM + // 'punctuation.definition.string.begin.php' + ]); + expect(editor).toHaveScopesAtPosition([1, 17], 'b', ['source.php', "meta.method-call.php", "string.quoted.single.php"]); + expect(editor).toHaveScopesAtPosition([1, 18], "'", ["source.php", "meta.method-call.php", "string.quoted.single.php", + // FIXME following scopes differ from TM + // 'punctuation.definition.string.end.php' + ]); + expect(editor).toHaveScopesAtPosition([1, 19], ')', ["source.php", "meta.method-call.php", "punctuation.definition.arguments.end.bracket.round.php"]); + }); + }); + + describe("closures", () => { + it("should tokenize closures correctly", () => { + editor.setPhpText("$a = function() /* use($b) */ {};"); + + expect(editor).toHaveScopesAtPosition([1, 0], '$', ["source.php", "variable.other.php", "punctuation.definition.variable.php"]); + expect(editor).toHaveScopesAtPosition([1, 1], 'a', ['source.php', "variable.other.php"]); + expect(editor).toHaveScopesAtPosition([1, 2], ' ', ['source.php']); + expect(editor).toHaveScopesAtPosition([1, 3], '=', ['source.php', "keyword.operator.assignment.php"]); + expect(editor).toHaveScopesAtPosition([1, 4], ' ', ['source.php']); + expect(editor).toHaveScopesAtPosition([1, 5], 'function', ['source.php', "meta.function.closure.php", "storage.type.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 13], '(', ["source.php", "meta.function.closure.php", "punctuation.definition.parameters.begin.bracket.round.php", + // FIXME following scopes differ from TM + "meta.function.parameters.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 14], ')', ["source.php", "meta.function.closure.php", "punctuation.definition.parameters.end.bracket.round.php", + // FIXME following scopes differ from TM + "meta.function.parameters.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 16], '/*', ["source.php", "meta.function.closure.php", "comment.block.php", + // FIXME following scopes differ from TM + // "punctuation.definition.comment.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 27], '*/', ["source.php", "meta.function.closure.php", "comment.block.php", + // FIXME following scopes differ from TM + // "punctuation.definition.comment.php" + ]); + }); + + it("tokenizes parameters", () => { + editor.setPhpText("function($a, string $b) {}"); + + expect(editor).toHaveScopesAtPosition([1, 0], 'function', ['source.php', "meta.function.closure.php", "storage.type.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 8], '(', ["source.php", "meta.function.closure.php", "punctuation.definition.parameters.begin.bracket.round.php", + // FIXME following scopes differ from TM + "meta.function.parameters.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 9], '$', ["source.php", "meta.function.closure.php", "meta.function.parameters.php", "meta.function.parameter.no-default.php", "variable.other.php", "punctuation.definition.variable.php"]); + expect(editor).toHaveScopesAtPosition([1, 10], 'a', ["source.php", "meta.function.closure.php", "meta.function.parameters.php", "meta.function.parameter.no-default.php", "variable.other.php"]); + expect(editor).toHaveScopesAtPosition([1, 11], ',', ["source.php", "meta.function.closure.php", "meta.function.parameters.php", "punctuation.separator.delimiter.php"]); + expect(editor).toHaveScopesAtPosition([1, 12], ' ', ["source.php", "meta.function.closure.php", "meta.function.parameters.php"]); + expect(editor).toHaveScopesAtPosition([1, 13], 'string', ["source.php", "meta.function.closure.php", "meta.function.parameters.php", "keyword.other.type.php", "meta.function.parameter.no-default.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 19], ' ', ["source.php", "meta.function.closure.php", "meta.function.parameters.php", "meta.function.parameter.no-default.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 20], '$', ["source.php", "meta.function.closure.php", "meta.function.parameters.php", "variable.other.php", "punctuation.definition.variable.php", "meta.function.parameter.no-default.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 21], 'b', ["source.php", "meta.function.closure.php", "meta.function.parameters.php", "variable.other.php", "meta.function.parameter.no-default.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 22], ')', ["source.php", "meta.function.closure.php", "punctuation.definition.parameters.end.bracket.round.php", + // FIXME following scopes differ from TM + "meta.function.parameters.php" + ]); + }); + + it("tokenizes return values", async () => { + editor.setPhpText("function() : string {}"); + + expect(editor).toHaveScopesAtPosition([1, 0], 'function', ['source.php', "meta.function.closure.php", "storage.type.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 8], '(', ["source.php", "meta.function.closure.php", "punctuation.definition.parameters.begin.bracket.round.php", + // FIXME following scopes differ from TM + "meta.function.parameters.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 9], ')', ["source.php", "meta.function.closure.php", "punctuation.definition.parameters.end.bracket.round.php", + // FIXME following scopes differ from TM + "meta.function.parameters.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 10], ' ', ['source.php', "meta.function.closure.php"]); + expect(editor).toHaveScopesAtPosition([1, 11], ':', ["source.php", "meta.function.closure.php", "keyword.operator.return-value.php"]); + expect(editor).toHaveScopesAtPosition([1, 12], ' ', ['source.php', "meta.function.closure.php"]); + expect(editor).toHaveScopesAtPosition([1, 13], 'string', ['source.php', "meta.function.closure.php", "keyword.other.type.php"]); + expect(editor).toHaveScopesAtPosition([1, 19], ' ', ["source.php", + // FIXME following scopes differ from TM + "meta.function.closure.php"]); + expect(editor).toHaveScopesAtPosition([1, 20], '{', ["source.php", "punctuation.definition.begin.bracket.curly.php", + // FIXME following scopes differ from TM + "meta.function.closure.php"]); + expect(editor).toHaveScopesAtPosition([1, 21], '}', ["source.php", "punctuation.definition.end.bracket.curly.php", + // FIXME following scopes differ from TM + "meta.function.closure.php"]); + + editor.setPhpText("function() : \\Client {}"); + await nextHighlightingUpdate(editor) + + expect(editor).toHaveScopesAtPosition([1, 0], 'function', ['source.php', "meta.function.closure.php", "storage.type.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 11], ':', ["source.php", "meta.function.closure.php", "keyword.operator.return-value.php"]); + expect(editor).toHaveScopesAtPosition([1, 13], '\\', ["source.php", "meta.function.closure.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 14], 'Client', ['source.php', "meta.function.closure.php", "support.class.php"]); + }); + + it("tokenizes nullable return values", async () => { + editor.setPhpText("function() :? Client {}"); + + expect(editor).toHaveScopesAtPosition([1, 0], 'function', ['source.php', "meta.function.closure.php", "storage.type.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 11], ':', ["source.php", "meta.function.closure.php", "keyword.operator.return-value.php"]); + expect(editor).toHaveScopesAtPosition([1, 12], '?', ["source.php", "meta.function.closure.php", "keyword.operator.nullable-type.php"]); + expect(editor).toHaveScopesAtPosition([1, 14], 'Client', ['source.php', "meta.function.closure.php", "support.class.php"]); + + editor.setPhpText("function(): ?Client {}"); + await nextHighlightingUpdate(editor) + + expect(editor).toHaveScopesAtPosition([1, 10], ':', ["source.php", "meta.function.closure.php", "keyword.operator.return-value.php"]); + expect(editor).toHaveScopesAtPosition([1, 12], '?', ["source.php", "meta.function.closure.php", "keyword.operator.nullable-type.php"]); + expect(editor).toHaveScopesAtPosition([1, 13], 'Client', ['source.php', "meta.function.closure.php", "support.class.php"]); + }); + + it("tokenizes closure returning reference", async () => { + editor.setPhpText("function&() {}"); + + expect(editor).toHaveScopesAtPosition([1, 0], 'function', ['source.php', "meta.function.closure.php", "storage.type.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 8], '&', ["source.php", "meta.function.closure.php", "storage.modifier.reference.php"]); + expect(editor).toHaveScopesAtPosition([1, 9], '(', ["source.php", "meta.function.closure.php", "punctuation.definition.parameters.begin.bracket.round.php", + // FIXME following scopes differ from TM + "meta.function.parameters.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 10], ')', ["source.php", "meta.function.closure.php", "punctuation.definition.parameters.end.bracket.round.php", + // FIXME following scopes differ from TM + "meta.function.parameters.php" + ]); + + editor.setPhpText("function &() {}"); + await nextHighlightingUpdate(editor) + + expect(editor).toHaveScopesAtPosition([1, 8], ' ', ['source.php', "meta.function.closure.php"]); + expect(editor).toHaveScopesAtPosition([1, 9], '&', ["source.php", "meta.function.closure.php", "storage.modifier.reference.php"]); + expect(editor).toHaveScopesAtPosition([1, 10], '(', ["source.php", "meta.function.closure.php", "punctuation.definition.parameters.begin.bracket.round.php", + // FIXME following scopes differ from TM + "meta.function.parameters.php" + ]); + }); + + it("tokenizes use inheritance", async () => { + editor.setPhpText("function () use($a) {}"); + + expect(editor).toHaveScopesAtPosition([1, 0], 'function', ['source.php', "meta.function.closure.php", "storage.type.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 12], 'use', ["source.php", "meta.function.closure.php", "meta.function.closure.use.php", "keyword.other.function.use.php"]); + expect(editor).toHaveScopesAtPosition([1, 15], '(', ["source.php", "meta.function.closure.php", "meta.function.closure.use.php", "punctuation.definition.parameters.begin.bracket.round.php"]); + expect(editor).toHaveScopesAtPosition([1, 16], '$', ["source.php", "meta.function.closure.php", "meta.function.closure.use.php", "variable.other.php", "punctuation.definition.variable.php"]); + expect(editor).toHaveScopesAtPosition([1, 17], 'a', ["source.php", "meta.function.closure.php", "meta.function.closure.use.php", "variable.other.php"]); + expect(editor).toHaveScopesAtPosition([1, 18], ')', ["source.php", "meta.function.closure.php", "meta.function.closure.use.php", "punctuation.definition.parameters.end.bracket.round.php"]); + + editor.setPhpText("function () use($a ,$b) {}"); + await nextHighlightingUpdate(editor) + + expect(editor).toHaveScopesAtPosition([1, 0], 'function', ['source.php', "meta.function.closure.php", "storage.type.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 12], 'use', ["source.php", "meta.function.closure.php", "meta.function.closure.use.php", "keyword.other.function.use.php"]); + expect(editor).toHaveScopesAtPosition([1, 15], '(', ["source.php", "meta.function.closure.php", "meta.function.closure.use.php", "punctuation.definition.parameters.begin.bracket.round.php"]); + expect(editor).toHaveScopesAtPosition([1, 16], '$', ["source.php", "meta.function.closure.php", "meta.function.closure.use.php", "variable.other.php", "punctuation.definition.variable.php"]); + expect(editor).toHaveScopesAtPosition([1, 17], 'a', ["source.php", "meta.function.closure.php", "meta.function.closure.use.php", "variable.other.php"]); + expect(editor).toHaveScopesAtPosition([1, 19], ',', ["source.php", "meta.function.closure.php", "meta.function.closure.use.php", "punctuation.separator.delimiter.php"]); + expect(editor).toHaveScopesAtPosition([1, 20], '$', ["source.php", "meta.function.closure.php", "meta.function.closure.use.php", "variable.other.php", "punctuation.definition.variable.php"]); + expect(editor).toHaveScopesAtPosition([1, 21], 'b', ["source.php", "meta.function.closure.php", "meta.function.closure.use.php", "variable.other.php"]); + expect(editor).toHaveScopesAtPosition([1, 22], ')', ["source.php", "meta.function.closure.php", "meta.function.closure.use.php", "punctuation.definition.parameters.end.bracket.round.php"]); + }); + + it("tokenizes use inheritance by reference", () => { + editor.setPhpText("function () use( &$a ) {}"); + + expect(editor).toHaveScopesAtPosition([1, 0], 'function', ['source.php', "meta.function.closure.php", "storage.type.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 12], 'use', ["source.php", "meta.function.closure.php", "meta.function.closure.use.php", "keyword.other.function.use.php"]); + expect(editor).toHaveScopesAtPosition([1, 17], '&', ["source.php", "meta.function.closure.php", "meta.function.closure.use.php", "storage.modifier.reference.php", + // FIXME following scopes differ from TM + // TS does not include the '&' in the variable node + // "variable.other.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 18], '$', ["source.php", "meta.function.closure.php", "meta.function.closure.use.php", "variable.other.php", "punctuation.definition.variable.php"]); + expect(editor).toHaveScopesAtPosition([1, 19], 'a', ["source.php", "meta.function.closure.php", "meta.function.closure.use.php", "variable.other.php"]); + }); + + it("tokenizes trailing comma in closure parameters and use inheritance", () => { + editor.setPhpText("function($a,)use($b,){}"); + + expect(editor).toHaveScopesAtPosition([1, 0], 'function', ['source.php', "meta.function.closure.php", "storage.type.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 9], '$', ["source.php", "meta.function.closure.php", "meta.function.parameters.php", "meta.function.parameter.no-default.php", "variable.other.php", "punctuation.definition.variable.php"]); + expect(editor).toHaveScopesAtPosition([1, 10], 'a', ["source.php", "meta.function.closure.php", "meta.function.parameters.php", "meta.function.parameter.no-default.php", "variable.other.php"]); + expect(editor).toHaveScopesAtPosition([1, 11], ',', ["source.php", "meta.function.closure.php", "meta.function.parameters.php", "punctuation.separator.delimiter.php"]); + expect(editor).toHaveScopesAtPosition([1, 13], 'use', ["source.php", "meta.function.closure.php", "meta.function.closure.use.php", "keyword.other.function.use.php"]); + expect(editor).toHaveScopesAtPosition([1, 17], '$', ["source.php", "meta.function.closure.php", "meta.function.closure.use.php", "variable.other.php", "punctuation.definition.variable.php"]); + expect(editor).toHaveScopesAtPosition([1, 18], 'b', ["source.php", "meta.function.closure.php", "meta.function.closure.use.php", "variable.other.php"]); + expect(editor).toHaveScopesAtPosition([1, 19], ',', ["source.php", "meta.function.closure.php", "meta.function.closure.use.php", "punctuation.separator.delimiter.php"]); + }); + }); + + describe('arrow functions', () => { + it('tokenizes arrow functions', () => { + editor.setPhpText('$pow = fn($x) => $x * 2;'); + + expect(editor).toHaveScopesAtPosition([1, 7], 'fn', ['source.php', "meta.function.closure.php", "storage.type.function.php"]) + expect(editor).toHaveScopesAtPosition([1, 9], '(', ['source.php', "meta.function.closure.php", "punctuation.definition.parameters.begin.bracket.round.php", + // FIXME following scopes differ from TM + "meta.function.parameters.php" + ]) + expect(editor).toHaveScopesAtPosition([1, 10], '$', ['source.php', "meta.function.closure.php", "meta.function.parameters.php", "meta.function.parameter.no-default.php", "variable.other.php", "punctuation.definition.variable.php"]) + expect(editor).toHaveScopesAtPosition([1, 11], 'x', ['source.php', "meta.function.closure.php", "meta.function.parameters.php", "meta.function.parameter.no-default.php", "variable.other.php"]) + expect(editor).toHaveScopesAtPosition([1, 12], ')', ['source.php', "meta.function.closure.php", "punctuation.definition.parameters.end.bracket.round.php", + // FIXME following scopes differ from TM + "meta.function.parameters.php" + ]) + expect(editor).toHaveScopesAtPosition([1, 13], ' ', ['source.php', "meta.function.closure.php"]) + expect(editor).toHaveScopesAtPosition([1, 14], '=>', ['source.php', "meta.function.closure.php", "punctuation.definition.arrow.php"]) + expect(editor).toHaveScopesAtPosition([1, 16], ' ', ['source.php', "meta.function.closure.php"]) + expect(editor).toHaveScopesAtPosition([1, 17], '$', ['source.php', "variable.other.php", "punctuation.definition.variable.php", + // FIXME following scopes differ from TM + "meta.function.closure.php" + ]) + expect(editor).toHaveScopesAtPosition([1, 18], 'x', ['source.php', "variable.other.php", + // FIXME following scopes differ from TM + "meta.function.closure.php", + ]) + expect(editor).toHaveScopesAtPosition([1, 19], ' ', ['source.php', "meta.function.closure.php"]) + expect(editor).toHaveScopesAtPosition([1, 20], '*', ['source.php', "keyword.operator.arithmetic.php", + // FIXME following scopes differ from TM + "meta.function.closure.php", + ]) + expect(editor).toHaveScopesAtPosition([1, 21], ' ', ['source.php', "meta.function.closure.php"]) + expect(editor).toHaveScopesAtPosition([1, 22], '2', ['source.php', "constant.numeric.decimal.php", + // FIXME following scopes differ from TM + "meta.function.closure.php", + ]) + expect(editor).toHaveScopesAtPosition([1, 23], ';', ['source.php', "punctuation.terminator.expression.php"]) + + }); + + it('tokenizes parameters', () => { + editor.setPhpText('$pow = fn(int $x=0) => $x * 2;') + + expect(editor).toHaveScopesAtPosition([1, 7], 'fn', ['source.php', "meta.function.closure.php", "storage.type.function.php"]) + expect(editor).toHaveScopesAtPosition([1, 9], '(', ['source.php', "meta.function.closure.php", "punctuation.definition.parameters.begin.bracket.round.php", + // FIXME following scopes differ from TM + "meta.function.parameters.php" + ]) + expect(editor).toHaveScopesAtPosition([1, 10], 'int', ['source.php', "meta.function.closure.php", "meta.function.parameters.php", "keyword.other.type.php", "meta.function.parameter.default.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php", + ]) + expect(editor).toHaveScopesAtPosition([1, 14], '$', ['source.php', "meta.function.closure.php", "meta.function.parameters.php", "variable.other.php", "punctuation.definition.variable.php", "meta.function.parameter.default.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php", + ]) + expect(editor).toHaveScopesAtPosition([1, 15], 'x', ['source.php', "meta.function.closure.php", "meta.function.parameters.php", "variable.other.php", "meta.function.parameter.default.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php", + ]) + expect(editor).toHaveScopesAtPosition([1, 16], '=', ['source.php', "meta.function.closure.php", "meta.function.parameters.php", "keyword.operator.assignment.php", "meta.function.parameter.default.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php", + ]) + expect(editor).toHaveScopesAtPosition([1, 17], '0', ['source.php', "meta.function.closure.php", "meta.function.parameters.php", "constant.numeric.decimal.php", "meta.function.parameter.default.php", + // FIXME following scopes differ from TM + // "meta.function.parameter.typehinted.php", + ]) + expect(editor).toHaveScopesAtPosition([1, 18], ')', ['source.php', "meta.function.closure.php", "punctuation.definition.parameters.end.bracket.round.php", + // FIXME following scopes differ from TM + "meta.function.parameters.php" + ]) + }); + + it('tokenizes return types', () => { + editor.setPhpText('$pow = fn($x) :? int => $x * 2;') + expect(editor).toHaveScopesAtPosition([1, 7], 'fn', ['source.php', "meta.function.closure.php", "storage.type.function.php"]) + expect(editor).toHaveScopesAtPosition([1, 14], ':', ['source.php', "meta.function.closure.php", "keyword.operator.return-value.php"]) + expect(editor).toHaveScopesAtPosition([1, 15], '?', ['source.php', "meta.function.closure.php", "keyword.operator.nullable-type.php"]) + expect(editor).toHaveScopesAtPosition([1, 17], 'int', ['source.php', "meta.function.closure.php", "keyword.other.type.php"]) + }); + }); + + describe("the scope resolution operator", () => { + it("tokenizes static method calls with no arguments", async () => { + editor.setPhpText("obj::method()"); + + expect(editor).toHaveScopesAtPosition([1, 0], 'obj', ["source.php", "support.class.php", + // FIXME following scopes differ from TM + "meta.method-call.static.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 3], '::', ["source.php", "meta.method-call.static.php", "keyword.operator.class.php"]); + expect(editor).toHaveScopesAtPosition([1, 5], 'method', ["source.php", "meta.method-call.static.php", "entity.name.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 11], '(', ["source.php", "meta.method-call.static.php", "punctuation.definition.arguments.begin.bracket.round.php"]); + expect(editor).toHaveScopesAtPosition([1, 12], ')', ["source.php", "meta.method-call.static.php", "punctuation.definition.arguments.end.bracket.round.php"]); + + editor.setPhpText("obj :: method ()"); + await nextHighlightingUpdate(editor); + + expect(editor).toHaveScopesAtPosition([1, 0], 'obj', ["source.php", "support.class.php", + // FIXME following scopes differ from TM + "meta.method-call.static.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 3], ' ', ["source.php", + // FIXME following scopes differ from TM + "meta.method-call.static.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 4], '::', ["source.php", "meta.method-call.static.php", "keyword.operator.class.php"]); + expect(editor).toHaveScopesAtPosition([1, 6], ' ', ['source.php', "meta.method-call.static.php"]); + expect(editor).toHaveScopesAtPosition([1, 7], 'method', ["source.php", "meta.method-call.static.php", "entity.name.function.php"]); + }); + + it("tokenizes static method calls with arguments", () => { + editor.setPhpText("obj::method(5, 'b')"); + + expect(editor).toHaveScopesAtPosition([1, 0], 'obj', ["source.php", "support.class.php", + // FIXME following scopes differ from TM + "meta.method-call.static.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 3], '::', ["source.php", "meta.method-call.static.php", "keyword.operator.class.php"]); + expect(editor).toHaveScopesAtPosition([1, 5], 'method', ["source.php", "meta.method-call.static.php", "entity.name.function.php"]); + expect(editor).toHaveScopesAtPosition([1, 11], '(', ["source.php", "meta.method-call.static.php", "punctuation.definition.arguments.begin.bracket.round.php"]); + expect(editor).toHaveScopesAtPosition([1, 12], '5', ["source.php", "meta.method-call.static.php", "constant.numeric.decimal.php"]); + expect(editor).toHaveScopesAtPosition([1, 13], ',', ["source.php", "meta.method-call.static.php", "punctuation.separator.delimiter.php"]); + expect(editor).toHaveScopesAtPosition([1, 14], ' ', ['source.php', "meta.method-call.static.php"]); + expect(editor).toHaveScopesAtPosition([1, 15], "'", ["source.php", "meta.method-call.static.php", "string.quoted.single.php", + // FIXME following scopes differ from TM + // "punctuation.definition.string.begin.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 16], 'b', ["source.php", "meta.method-call.static.php", "string.quoted.single.php"]); + expect(editor).toHaveScopesAtPosition([1, 17], "'", ["source.php", "meta.method-call.static.php", "string.quoted.single.php", + // FIXME following scopes differ from TM + // "punctuation.definition.string.end.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 18], ')', ["source.php", "meta.method-call.static.php", "punctuation.definition.arguments.end.bracket.round.php"]); + }); + + it("tokenizes class variables", () => { + editor.setPhpText("obj::$variable"); + + expect(editor).toHaveScopesAtPosition([1, 0], 'obj', ['source.php', "support.class.php"]); + expect(editor).toHaveScopesAtPosition([1, 3], '::', ['source.php', "keyword.operator.class.php"]); + expect(editor).toHaveScopesAtPosition([1, 5], '$', ["source.php", "variable.other.class.php", "punctuation.definition.variable.php"]); + expect(editor).toHaveScopesAtPosition([1, 6], 'variable', ['source.php', "variable.other.class.php"]); + }); + + it("tokenizes class constants", () => { + editor.setPhpText("obj::constant"); + + expect(editor).toHaveScopesAtPosition([1, 0], 'obj', ['source.php', "support.class.php"]); + expect(editor).toHaveScopesAtPosition([1, 3], '::', ['source.php', "keyword.operator.class.php"]); + expect(editor).toHaveScopesAtPosition([1, 5], 'constant', ['source.php', "constant.other.class.php"]); + }); + + it("tokenizes namespaced classes", () => { + editor.setPhpText("\\One\\Two\\Three::$var"); + + expect(editor).toHaveScopesAtPosition([1, 0], '\\', ["source.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // 'punctuation.separator.inheritance.php' + ]); + expect(editor).toHaveScopesAtPosition([1, 1], 'One', ['source.php', "support.other.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 4], '\\', ["source.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // 'punctuation.separator.inheritance.php' + ]); + expect(editor).toHaveScopesAtPosition([1, 5], 'Two', ['source.php', "support.other.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 8], '\\', ["source.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // 'punctuation.separator.inheritance.php' + ]); + expect(editor).toHaveScopesAtPosition([1, 9], 'Three', ['source.php', "support.class.php"]); + expect(editor).toHaveScopesAtPosition([1, 14], '::', ['source.php', "keyword.operator.class.php"]); + expect(editor).toHaveScopesAtPosition([1, 16], '$', ["source.php", "variable.other.class.php", "punctuation.definition.variable.php"]); + expect(editor).toHaveScopesAtPosition([1, 17], 'var', ['source.php', "variable.other.class.php"]); + }); + + it('tokenizes the special "class" keyword', async () => { + editor.setPhpText("obj::class"); + + expect(editor).toHaveScopesAtPosition([1, 0], 'obj', ['source.php', "support.class.php"]); + expect(editor).toHaveScopesAtPosition([1, 3], '::', ['source.php', "keyword.operator.class.php"]); + expect(editor).toHaveScopesAtPosition([1, 5], 'class', ['source.php', "keyword.other.class.php"]); + + // Should NOT be tokenized as `keyword.other.class` + editor.setPhpText("obj::classic"); + await nextHighlightingUpdate(editor) + + expect(editor).toHaveScopesAtPosition([1, 0], 'obj', ['source.php', "support.class.php"]); + expect(editor).toHaveScopesAtPosition([1, 3], '::', ['source.php', "keyword.operator.class.php"]); + expect(editor).toHaveScopesAtPosition([1, 5], 'classic', ['source.php', "constant.other.class.php"]); + }); + }); + + describe("try/catch", () => { + it("tokenizes a basic try/catch block", () => { + editor.setPhpText("try {} catch(Exception $e) {}"); + + expect(editor).toHaveScopesAtPosition([1, 0], 'try', ['source.php', "keyword.control.exception.php"]); + expect(editor).toHaveScopesAtPosition([1, 4], '{', ['source.php', "punctuation.definition.begin.bracket.curly.php"]); + expect(editor).toHaveScopesAtPosition([1, 5], '}', ['source.php', "punctuation.definition.end.bracket.curly.php"]); + expect(editor).toHaveScopesAtPosition([1, 7], 'catch', ['source.php', "meta.catch.php", "keyword.control.exception.catch.php"]); + expect(editor).toHaveScopesAtPosition([1, 12], '(', ["source.php", "meta.catch.php", "punctuation.definition.parameters.begin.bracket.round.php"]); + expect(editor).toHaveScopesAtPosition([1, 13], 'Exception', ['source.php', "meta.catch.php", "support.class.exception.php"]); + expect(editor).toHaveScopesAtPosition([1, 22], ' ', ['source.php', "meta.catch.php"]); + expect(editor).toHaveScopesAtPosition([1, 23], '$', ["source.php", "meta.catch.php", "variable.other.php", "punctuation.definition.variable.php"]); + expect(editor).toHaveScopesAtPosition([1, 24], 'e', ['source.php', "meta.catch.php", "variable.other.php"]); + expect(editor).toHaveScopesAtPosition([1, 25], ')', ["source.php", "meta.catch.php", "punctuation.definition.parameters.end.bracket.round.php"]); + expect(editor).toHaveScopesAtPosition([1, 27], '{', ["source.php", "punctuation.definition.begin.bracket.curly.php", + // FIXME following scopes differ from TM + "meta.catch.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 28], '}', ["source.php", "punctuation.definition.end.bracket.curly.php", + // FIXME following scopes differ from TM + "meta.catch.php" + ]); + }); + + it("tokenizes a catch block containing namespaced exception", () => { + editor.setPhpText("try {} catch(\\Abc\\Exception $e) {}"); + + expect(editor).toHaveScopesAtPosition([1, 7], 'catch', ['source.php', "meta.catch.php", "keyword.control.exception.catch.php"]); + expect(editor).toHaveScopesAtPosition([1, 13], '\\', ["source.php", "meta.catch.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 14], 'Abc', ['source.php', "meta.catch.php", "support.other.namespace.php"]); + expect(editor).toHaveScopesAtPosition([1, 17], '\\', ["source.php", "meta.catch.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]); + expect(editor).toHaveScopesAtPosition([1, 18], 'Exception', ['source.php', "meta.catch.php", "support.class.exception.php"]); + }); + + it('tokenizes a catch block containing multiple exceptions', () => { + editor.setPhpText('try {} catch(AException | BException | CException $e) {}') + + expect(editor).toHaveScopesAtPosition([1, 7], 'catch', ['source.php', 'meta.catch.php', 'keyword.control.exception.catch.php']) + expect(editor).toHaveScopesAtPosition([1, 12], '(', ['source.php', 'meta.catch.php', 'punctuation.definition.parameters.begin.bracket.round.php']) + expect(editor).toHaveScopesAtPosition([1, 13], 'AException', ['source.php', 'meta.catch.php', 'support.class.exception.php']) + expect(editor).toHaveScopesAtPosition([1, 23], ' ', ['source.php', 'meta.catch.php']) + expect(editor).toHaveScopesAtPosition([1, 24], '|', ['source.php', 'meta.catch.php', 'punctuation.separator.delimiter.php']) + expect(editor).toHaveScopesAtPosition([1, 25], ' ', ['source.php', 'meta.catch.php']) + expect(editor).toHaveScopesAtPosition([1, 26], 'BException', ['source.php', 'meta.catch.php', 'support.class.exception.php']) + expect(editor).toHaveScopesAtPosition([1, 37], '|', ['source.php', 'meta.catch.php', 'punctuation.separator.delimiter.php']) + expect(editor).toHaveScopesAtPosition([1, 39], 'CException', ['source.php', 'meta.catch.php', 'support.class.exception.php']) + expect(editor).toHaveScopesAtPosition([1, 50], '$', ['source.php', 'meta.catch.php', 'variable.other.php', 'punctuation.definition.variable.php']) + expect(editor).toHaveScopesAtPosition([1, 51], 'e', ['source.php', 'meta.catch.php', 'variable.other.php']) + expect(editor).toHaveScopesAtPosition([1, 52], ')', ['source.php', 'meta.catch.php', 'punctuation.definition.parameters.end.bracket.round.php']) + expect(editor).toHaveScopesAtPosition([1, 54], '{', ['source.php', 'punctuation.definition.begin.bracket.curly.php', + // FIXME following scopes differ from TM + 'meta.catch.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 55], '}', ['source.php', 'punctuation.definition.end.bracket.curly.php', + // FIXME following scopes differ from TM + 'meta.catch.php' + ]) + }) + + it('tokenizes a catch block containing multiple namespaced exceptions', () => { + editor.setPhpText('try {} catch(\\Abc\\Exception | \\Test\\Exception | \\Error $e) {}') + + expect(editor).toHaveScopesAtPosition([1, 7], 'catch', ["source.php", "meta.catch.php", "keyword.control.exception.catch.php"]) + expect(editor).toHaveScopesAtPosition([1, 12], '(', ["source.php", "meta.catch.php", "punctuation.definition.parameters.begin.bracket.round.php"]) + expect(editor).toHaveScopesAtPosition([1, 13], '\\', ["source.php", "meta.catch.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]) + expect(editor).toHaveScopesAtPosition([1, 14], 'Abc', ["source.php", "meta.catch.php", "support.other.namespace.php"]) + expect(editor).toHaveScopesAtPosition([1, 17], '\\', ["source.php", "meta.catch.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]) + expect(editor).toHaveScopesAtPosition([1, 18], 'Exception', ["source.php", "meta.catch.php", "support.class.exception.php"]) + expect(editor).toHaveScopesAtPosition([1, 27], ' ', ["source.php", "meta.catch.php"]) + expect(editor).toHaveScopesAtPosition([1, 28], '|', ["source.php", "meta.catch.php", "punctuation.separator.delimiter.php"]) + expect(editor).toHaveScopesAtPosition([1, 29], ' ', ["source.php", "meta.catch.php"]) + expect(editor).toHaveScopesAtPosition([1, 30], '\\', ["source.php", "meta.catch.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]) + expect(editor).toHaveScopesAtPosition([1, 31], 'Test', ["source.php", "meta.catch.php", "support.other.namespace.php"]) + expect(editor).toHaveScopesAtPosition([1, 35], '\\', ["source.php", "meta.catch.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]) + expect(editor).toHaveScopesAtPosition([1, 36], 'Exception', ["source.php", "meta.catch.php", "support.class.exception.php"]) + expect(editor).toHaveScopesAtPosition([1, 45], ' ', ["source.php", "meta.catch.php"]) + expect(editor).toHaveScopesAtPosition([1, 46], '|', ["source.php", "meta.catch.php", "punctuation.separator.delimiter.php"]) + expect(editor).toHaveScopesAtPosition([1, 47], ' ', ["source.php", "meta.catch.php"]) + expect(editor).toHaveScopesAtPosition([1, 48], '\\', ["source.php", "meta.catch.php", "support.other.namespace.php", + // FIXME following scopes differ from TM + // "punctuation.separator.inheritance.php" + ]) + expect(editor).toHaveScopesAtPosition([1, 49], 'Error', ["source.php", "meta.catch.php", "support.class.exception.php"]) + expect(editor).toHaveScopesAtPosition([1, 57], ')', ["source.php", "meta.catch.php", "punctuation.definition.parameters.end.bracket.round.php"]) + }) + + it('tokenizes non-capturing catch block', () => { + editor.setPhpText('try {} catch (Exception) {}') + + expect(editor).toHaveScopesAtPosition([1, 7], 'catch', ["source.php", "meta.catch.php", "keyword.control.exception.catch.php"]) + expect(editor).toHaveScopesAtPosition([1, 13], '(', ["source.php", "meta.catch.php", "punctuation.definition.parameters.begin.bracket.round.php"]) + expect(editor).toHaveScopesAtPosition([1, 14], 'Exception', ["source.php", "meta.catch.php", "support.class.exception.php"]) + expect(editor).toHaveScopesAtPosition([1, 23], ')', ["source.php", "meta.catch.php", "punctuation.definition.parameters.end.bracket.round.php"]) + expect(editor).toHaveScopesAtPosition([1, 25], '{', ["source.php", "punctuation.definition.begin.bracket.curly.php", + // FIXME following scopes differ from TM + 'meta.catch.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 26], '}', ["source.php", "punctuation.definition.end.bracket.curly.php", + // FIXME following scopes differ from TM + 'meta.catch.php' + ]) + }) + }); + + describe("numbers", () => { + it("tokenizes hexadecimals starting", async () => { + editor.setPhpText("0x1D306"); + expect(editor).toHaveScopesAtPosition([1, 0], ['source.php', "constant.numeric.hex.php"]); + + editor.setPhpText("0X1D306"); + await nextHighlightingUpdate(editor); + expect(editor).toHaveScopesAtPosition([1, 0], ['source.php', "constant.numeric.hex.php"]); + }); + + it("tokenizes binary literals", async () => { + editor.setPhpText("0b011101110111010001100110"); + expect(editor).toHaveScopesAtPosition([1, 0], '0b011101110111010001100110', ['source.php', "constant.numeric.binary.php"]); + + editor.setPhpText("0B011101110111010001100110"); + await nextHighlightingUpdate(editor); + expect(editor).toHaveScopesAtPosition([1, 0], '0B011101110111010001100110', ['source.php', "constant.numeric.binary.php"]); + }); + + it("tokenizes octal literals", async () => { + editor.setPhpText("01411"); + expect(editor).toHaveScopesAtPosition([1, 0], '01411', ['source.php', "constant.numeric.octal.php"]); + + editor.setPhpText("0010"); + await nextHighlightingUpdate(editor); + expect(editor).toHaveScopesAtPosition([1, 0], '0010', ['source.php', "constant.numeric.octal.php"]); + }); + + it("tokenizes decimals", async () => { + editor.setPhpText("0"); + expect(editor).toHaveScopesAtPosition([1, 0], '0', ['source.php', "constant.numeric.decimal.php"]); + + editor.setPhpText("1234"); + await nextHighlightingUpdate(editor); + expect(editor).toHaveScopesAtPosition([1, 0], '1234', ['source.php', "constant.numeric.decimal.php"]); + + editor.setPhpText("5e-10"); + await nextHighlightingUpdate(editor); + expect(editor).toHaveScopesAtPosition([1, 0], '5e-10', ['source.php', "constant.numeric.decimal.php"]); + + editor.setPhpText("5E+5"); + await nextHighlightingUpdate(editor); + expect(editor).toHaveScopesAtPosition([1, 0], '5E+5', ['source.php', "constant.numeric.decimal.php"]); + + editor.setPhpText("9."); + await nextHighlightingUpdate(editor); + expect(editor).toHaveScopesAtPosition([1, 0], '9', ['source.php', "constant.numeric.decimal.php"]); + expect(editor).toHaveScopesAtPosition([1, 1], '.', ["source.php", "constant.numeric.decimal.php", + // FIXME following scopes differ from TM + // 'punctuation.separator.decimal.period.php' + ]); + + editor.setPhpText(".9"); + await nextHighlightingUpdate(editor); + expect(editor).toHaveScopesAtPosition([1, 0], '.', ["source.php", "constant.numeric.decimal.php", + // FIXME following scopes differ from TM + // 'punctuation.separator.decimal.period.php' + ]); + expect(editor).toHaveScopesAtPosition([1, 1], '9', ['source.php', "constant.numeric.decimal.php"]); + + editor.setPhpText("9.9"); + await nextHighlightingUpdate(editor); + expect(editor).toHaveScopesAtPosition([1, 0], '9', ['source.php', "constant.numeric.decimal.php"]); + expect(editor).toHaveScopesAtPosition([1, 1], '.', ["source.php", "constant.numeric.decimal.php", + // FIXME following scopes differ from TM + // 'punctuation.separator.decimal.period.php' + ]); + expect(editor).toHaveScopesAtPosition([1, 2], '9', ['source.php', "constant.numeric.decimal.php"]); + + editor.setPhpText(".1e-23"); + await nextHighlightingUpdate(editor); + expect(editor).toHaveScopesAtPosition([1, 0], '.', ["source.php", "constant.numeric.decimal.php", + // FIXME following scopes differ from TM + // 'punctuation.separator.decimal.period.php' + ]); + expect(editor).toHaveScopesAtPosition([1, 1], '1e-23', ['source.php', "constant.numeric.decimal.php"]); + + editor.setPhpText("1.E3"); + await nextHighlightingUpdate(editor); + expect(editor).toHaveScopesAtPosition([1, 0], '1', ['source.php', "constant.numeric.decimal.php"]); + expect(editor).toHaveScopesAtPosition([1, 1], '.', ["source.php", "constant.numeric.decimal.php", + // FIXME following scopes differ from TM + // 'punctuation.separator.decimal.period.php' + ]); + expect(editor).toHaveScopesAtPosition([1, 2], 'E3', ['source.php', "constant.numeric.decimal.php"]); + }); + }); + + // SKIP numeric literal separators are handled by TS-php + // describe('numeric literal separator', () => {}) + + describe("switch and match", () => { + it("should tokenize switch statements correctly", () => { + editor.setPhpText(` + switch($foo) + { + case 'string': + return 1; + case 1: + break; + default: + continue; + } + `); + + expect(editor).toHaveScopesAtPosition([1, 0], 'switch', ["source.php", "meta.switch-statement.php", "keyword.control.switch.php"]); + expect(editor).toHaveScopesAtPosition([1, 6], '(', ["source.php", "meta.switch-statement.php", "punctuation.definition.switch-expression.begin.bracket.round.php"]); + expect(editor).toHaveScopesAtPosition([1, 7], '$', ["source.php", "meta.switch-statement.php", "variable.other.php", "punctuation.definition.variable.php"]); + expect(editor).toHaveScopesAtPosition([1, 8], 'foo', ['source.php', "meta.switch-statement.php", "variable.other.php"]); + expect(editor).toHaveScopesAtPosition([1, 13], ')', ["source.php", "meta.switch-statement.php", "punctuation.definition.switch-expression.end.bracket.round.php"]); + + expect(editor).toHaveScopesAtPosition([2, 0], '{', ["source.php", "meta.switch-statement.php", "punctuation.definition.section.switch-block.begin.bracket.curly.php"]); + + expect(editor).toHaveScopesAtPosition([3, 2], 'case', ['source.php', "meta.switch-statement.php", "keyword.control.case.php"]); + expect(editor).toHaveScopesAtPosition([3, 6], ' ', ['source.php', "meta.switch-statement.php"]); + expect(editor).toHaveScopesAtPosition([3, 7], "'", ["source.php", "meta.switch-statement.php", "string.quoted.single.php", + // FIXME following scopes differ from TM + // 'punctuation.definition.string.begin.php' + ]); + expect(editor).toHaveScopesAtPosition([3, 15], ':', ["source.php", "meta.switch-statement.php", "punctuation.terminator.statement.php"]); + + expect(editor).toHaveScopesAtPosition([4, 4], 'return', ["source.php", "meta.switch-statement.php", "keyword.control.return.php"]); + + expect(editor).toHaveScopesAtPosition([5, 2], 'case', ['source.php', "meta.switch-statement.php", "keyword.control.case.php"]); + expect(editor).toHaveScopesAtPosition([5, 6], ' ', ['source.php', "meta.switch-statement.php"]); + expect(editor).toHaveScopesAtPosition([5, 7], '1', ["source.php", "meta.switch-statement.php", "constant.numeric.decimal.php"]); + expect(editor).toHaveScopesAtPosition([5, 8], ':', ["source.php", "meta.switch-statement.php", "punctuation.terminator.statement.php"]); + + expect(editor).toHaveScopesAtPosition([6, 4], 'break', ['source.php', "meta.switch-statement.php", "keyword.control.break.php"]); + expect(editor).toHaveScopesAtPosition([7, 2], 'default', ["source.php", "meta.switch-statement.php", "keyword.control.default.php"]); + expect(editor).toHaveScopesAtPosition([7, 9], ':', ["source.php", "meta.switch-statement.php", "punctuation.terminator.statement.php"]); + expect(editor).toHaveScopesAtPosition([8, 4], 'continue', ["source.php", "meta.switch-statement.php", "keyword.control.continue.php"]); + expect(editor).toHaveScopesAtPosition([9, 0], '}', ["source.php", "meta.switch-statement.php", "punctuation.definition.section.switch-block.end.bracket.curly.php"]); + }); + + it('should tokenize match statements correctly', () => { + editor.setPhpText(` + echo match (1) { + 0 => 'Foo', + 1, 2 => 'Bar', + default => 'Baz', + }; + `); + + expect(editor).toHaveScopesAtPosition([1, 0], 'echo', ['source.php', 'support.function.construct.output.php']) + expect(editor).toHaveScopesAtPosition([1, 5], 'match', ['source.php', 'meta.match-statement.php', 'keyword.control.match.php']) + expect(editor).toHaveScopesAtPosition([1, 11], '(', ['source.php', 'meta.match-statement.php', 'punctuation.definition.match-expression.begin.bracket.round.php']) + expect(editor).toHaveScopesAtPosition([1, 12], '1', ['source.php', 'meta.match-statement.php', 'constant.numeric.decimal.php']) + expect(editor).toHaveScopesAtPosition([1, 13], ')', ['source.php', 'meta.match-statement.php', 'punctuation.definition.match-expression.end.bracket.round.php']) + expect(editor).toHaveScopesAtPosition([1, 15], '{', ['source.php', 'meta.match-statement.php', 'punctuation.definition.section.match-block.begin.bracket.curly.php']) + + expect(editor).toHaveScopesAtPosition([2, 4], '0', ['source.php', 'meta.match-statement.php', 'constant.numeric.decimal.php']) + expect(editor).toHaveScopesAtPosition([2, 6], '=>', ['source.php', 'meta.match-statement.php', 'keyword.definition.arrow.php']) + expect(editor).toHaveScopesAtPosition([2, 9], '\'', ['source.php', 'meta.match-statement.php', 'string.quoted.single.php', + // FIXME following scopes differ from TM + // 'punctuation.definition.string.begin.php' + ]) + expect(editor).toHaveScopesAtPosition([2, 10], 'Foo', ['source.php', 'meta.match-statement.php', 'string.quoted.single.php']) + expect(editor).toHaveScopesAtPosition([2, 13], '\'', ['source.php', 'meta.match-statement.php', 'string.quoted.single.php', + // FIXME following scopes differ from TM + // 'punctuation.definition.string.end.php' + ]) + expect(editor).toHaveScopesAtPosition([2, 14], ',', ['source.php', 'meta.match-statement.php', 'punctuation.separator.delimiter.php']) + + expect(editor).toHaveScopesAtPosition([3, 4], '1', ['source.php', 'meta.match-statement.php', 'constant.numeric.decimal.php']) + expect(editor).toHaveScopesAtPosition([3, 5], ',', ['source.php', 'meta.match-statement.php', 'punctuation.separator.delimiter.php']) + expect(editor).toHaveScopesAtPosition([3, 7], '2', ['source.php', 'meta.match-statement.php', 'constant.numeric.decimal.php']) + expect(editor).toHaveScopesAtPosition([3, 9], '=>', ['source.php', 'meta.match-statement.php', 'keyword.definition.arrow.php']) + expect(editor).toHaveScopesAtPosition([3, 12], '\'', ['source.php', 'meta.match-statement.php', 'string.quoted.single.php', + // FIXME following scopes differ from TM + // 'punctuation.definition.string.begin.php' + ]) + expect(editor).toHaveScopesAtPosition([3, 13], 'Bar', ['source.php', 'meta.match-statement.php', 'string.quoted.single.php']) + expect(editor).toHaveScopesAtPosition([3, 16], '\'', ['source.php', 'meta.match-statement.php', 'string.quoted.single.php', + // FIXME following scopes differ from TM + // 'punctuation.definition.string.end.php' + ]) + expect(editor).toHaveScopesAtPosition([3, 17], ',', ['source.php', 'meta.match-statement.php', 'punctuation.separator.delimiter.php']) + + expect(editor).toHaveScopesAtPosition([4, 4], 'default', ['source.php', 'meta.match-statement.php', 'keyword.control.default.php']) + expect(editor).toHaveScopesAtPosition([4, 12], '=>', ['source.php', 'meta.match-statement.php', 'keyword.definition.arrow.php']) + expect(editor).toHaveScopesAtPosition([4, 15], '\'', ['source.php', 'meta.match-statement.php', 'string.quoted.single.php', + // FIXME following scopes differ from TM + // 'punctuation.definition.string.begin.php' + ]) + expect(editor).toHaveScopesAtPosition([4, 16], 'Baz', ['source.php', 'meta.match-statement.php', 'string.quoted.single.php']) + expect(editor).toHaveScopesAtPosition([4, 19], '\'', ['source.php', 'meta.match-statement.php', 'string.quoted.single.php', + // FIXME following scopes differ from TM + // 'punctuation.definition.string.end.php' + ]) + expect(editor).toHaveScopesAtPosition([4, 20], ',', ['source.php', 'meta.match-statement.php', 'punctuation.separator.delimiter.php']) + + expect(editor).toHaveScopesAtPosition([5, 0], '}', ['source.php', 'meta.match-statement.php', 'punctuation.definition.section.match-block.end.bracket.curly.php']) + expect(editor).toHaveScopesAtPosition([5, 1], ';', ['source.php', 'punctuation.terminator.expression.php']) + }); + }); + + // SKIP b/c these tests are just parse/whitespace tests and this behvaior is + // handled by TS-php + // it('should tokenize storage types correctly', () => {}) + + describe('attributes', () => { + it('should tokenize basic attribute', () => { + editor.setPhpText(` + #[ExampleAttribute] + class Foo {} + `) + + expect(editor).toHaveScopesAtPosition([1, 0], '#[', ['source.php', 'meta.attribute.php', + // FIXME following scopes differ from TM + 'meta.class.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 2], 'ExampleAttribute', ['source.php', 'meta.attribute.php', 'support.attribute.php', + // FIXME following scopes differ from TM + 'meta.class.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 18], ']', ['source.php', 'meta.attribute.php', + // FIXME following scopes differ from TM + 'meta.class.php' + ]) + expect(editor).toHaveScopesAtPosition([2, 0], 'class', ['source.php', 'meta.class.php', 'storage.type.class.php']) + expect(editor).toHaveScopesAtPosition([2, 6], 'Foo', ['source.php', 'meta.class.php', 'entity.name.type.class.php']) + }) + + it('should tokenize inline attribute', () => { + editor.setPhpText('#[ExampleAttribute] class Foo {}') + + expect(editor).toHaveScopesAtPosition([1, 0], '#[', ['source.php', 'meta.attribute.php', + // FIXME following scopes differ from TM + 'meta.class.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 2], 'ExampleAttribute', ['source.php', 'meta.attribute.php', 'support.attribute.php', + // FIXME following scopes differ from TM + 'meta.class.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 18], ']', ['source.php', 'meta.attribute.php', + // FIXME following scopes differ from TM + 'meta.class.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 20], 'class', ['source.php', 'meta.class.php', 'storage.type.class.php']) + expect(editor).toHaveScopesAtPosition([1, 26], 'Foo', ['source.php', 'meta.class.php', 'entity.name.type.class.php']) + }) + + it('should tokenize parameter attribute', () => { + editor.setPhpText('function Foo(#[ParameterAttribute] $parameter) {}') + + expect(editor).toHaveScopesAtPosition([1, 0], 'function', ['source.php', 'meta.function.php', 'storage.type.function.php']) + expect(editor).toHaveScopesAtPosition([1, 9], 'Foo', ['source.php', 'meta.function.php', 'entity.name.function.php']) + expect(editor).toHaveScopesAtPosition([1, 12], '(', ['source.php', 'meta.function.php', 'punctuation.definition.parameters.begin.bracket.round.php', + // FIXME following scopes differ from TM + 'meta.function.parameters.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 13], '#[', ['source.php', 'meta.function.php', 'meta.function.parameters.php', 'meta.attribute.php', + // FIXME following scopes differ from TM + 'meta.function.parameter.no-default.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 15], 'ParameterAttribute', ['source.php', 'meta.function.php', 'meta.function.parameters.php', 'meta.attribute.php', 'support.attribute.php', + // FIXME following scopes differ from TM + 'meta.function.parameter.no-default.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 33], ']', ['source.php', 'meta.function.php', 'meta.function.parameters.php', 'meta.attribute.php', + // FIXME following scopes differ from TM + 'meta.function.parameter.no-default.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 35], '$', ['source.php', 'meta.function.php', 'meta.function.parameters.php', 'meta.function.parameter.no-default.php', 'variable.other.php', 'punctuation.definition.variable.php']) + expect(editor).toHaveScopesAtPosition([1, 36], 'parameter', ['source.php', 'meta.function.php', 'meta.function.parameters.php', 'meta.function.parameter.no-default.php', 'variable.other.php']) + expect(editor).toHaveScopesAtPosition([1, 45], ')', ['source.php', 'meta.function.php', 'punctuation.definition.parameters.end.bracket.round.php', + // FIXME following scopes differ from TM + 'meta.function.parameters.php' + ]) + }) + + it('should tokenize attribute for method', () => { + editor.setPhpText(` + class Foo { + #[ExampleAttribute] + public function bar() {} + # I'm a happy comment! + public function baz() {} + } + `) + + expect(editor).toHaveScopesAtPosition([2, 2], '#[', ['source.php', 'meta.class.php', 'meta.class.body.php', 'meta.attribute.php', + // FIXME following scopes differ from TM + 'meta.function.php' + ]) + expect(editor).toHaveScopesAtPosition([2, 4], 'ExampleAttribute', ['source.php', 'meta.class.php', 'meta.class.body.php', 'meta.attribute.php', 'support.attribute.php', + // FIXME following scopes differ from TM + 'meta.function.php' + ]) + expect(editor).toHaveScopesAtPosition([2, 20], ']', ['source.php', 'meta.class.php', 'meta.class.body.php', 'meta.attribute.php', + // FIXME following scopes differ from TM + 'meta.function.php' + ]) + expect(editor).toHaveScopesAtPosition([4, 0], ' ', ['source.php', 'meta.class.php', 'meta.class.body.php', + // FIXME following scopes differ from TM + // 'punctuation.whitespace.comment.leading.php' + ]) + expect(editor).toHaveScopesAtPosition([4, 2], '#', ['source.php', 'meta.class.php', 'meta.class.body.php', 'comment.line.number-sign.php', + // FIXME following scopes differ from TM + // 'punctuation.definition.comment.php' + ]) + expect(editor).toHaveScopesAtPosition([4, 3], ' I\'m a happy comment!', ['source.php', 'meta.class.php', 'meta.class.body.php', 'comment.line.number-sign.php']) + }) + + it('should tokenize attribute with namespace', () => { + // NOTE the TM test only includes the attribute w/o the class, which is + // invalid syntax + editor.setPhpText('#[Foo\\Bar\\Attribute] class foo {}') + + expect(editor).toHaveScopesAtPosition([1, 0], '#[', ['source.php', 'meta.attribute.php', + // FIXME following scopes differ from TM + 'meta.class.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 2], 'Foo', ['source.php', 'meta.attribute.php', 'support.other.namespace.php', + // FIXME following scopes differ from TM + 'meta.class.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 5], '\\', ['source.php', 'meta.attribute.php', 'support.other.namespace.php', + // FIXME following scopes differ from TM + 'meta.class.php' + // 'punctuation.separator.inheritance.php', + ]) + expect(editor).toHaveScopesAtPosition([1, 6], 'Bar', ['source.php', 'meta.attribute.php', 'support.other.namespace.php', + // FIXME following scopes differ from TM + 'meta.class.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 9], '\\', ['source.php', 'meta.attribute.php', 'support.other.namespace.php', + // FIXME following scopes differ from TM + 'meta.class.php', + // 'punctuation.separator.inheritance.php', + ]) + expect(editor).toHaveScopesAtPosition([1, 10], 'Attribute', ['source.php', 'meta.attribute.php', + // FIXME following scopes differ from TM + 'meta.class.php', + // 'support.attribute.php', + 'support.class.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 19], ']', ['source.php', 'meta.attribute.php', + // FIXME following scopes differ from TM + 'meta.class.php' + ]) + }) + + it('should tokenize multiple attributes', () => { + // NOTE the TM test only includes the attribute w/o the function, which is + // invalid syntax + editor.setPhpText('#[Attribute1, Attribute2] function foo() {}') + + expect(editor).toHaveScopesAtPosition([1, 0], '#[', ['source.php', 'meta.attribute.php', + // FIXME following scopes differ from TM + 'meta.function.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 2], 'Attribute1', ['source.php', 'meta.attribute.php', 'support.attribute.php', + // FIXME following scopes differ from TM + 'meta.function.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 12], ',', ['source.php', 'meta.attribute.php', 'punctuation.separator.delimiter.php', + // FIXME following scopes differ from TM + 'meta.function.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 14], 'Attribute2', ['source.php', 'meta.attribute.php', 'support.attribute.php', + // FIXME following scopes differ from TM + 'meta.function.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 24], ']', ['source.php', 'meta.attribute.php', + // FIXME following scopes differ from TM + 'meta.function.php' + ]) + }) + + it('should tokenize attribute with arguments', () => { + // NOTE the TM test only includes the attribute w/o the class, which is + // invalid syntax + editor.setPhpText('#[Attribute1, Attribute2(true, 2, [3.1, 3.2])] class foo {}') + + expect(editor).toHaveScopesAtPosition([1, 0], '#[', ['source.php', 'meta.attribute.php', + // FIXME following scopes differ from TM + 'meta.class.php', + ]) + expect(editor).toHaveScopesAtPosition([1, 2], 'Attribute1', ['source.php', 'meta.attribute.php', 'support.attribute.php', + // FIXME following scopes differ from TM + 'meta.class.php', + ]) + expect(editor).toHaveScopesAtPosition([1, 12], ',', ['source.php', 'meta.attribute.php', 'punctuation.separator.delimiter.php', + // FIXME following scopes differ from TM + 'meta.class.php', + ]) + expect(editor).toHaveScopesAtPosition([1, 14], 'Attribute2', ['source.php', 'meta.attribute.php', 'support.attribute.php', + // FIXME following scopes differ from TM + 'meta.class.php', + ]) + expect(editor).toHaveScopesAtPosition([1, 24], '(', ['source.php', 'meta.attribute.php', 'punctuation.definition.arguments.begin.bracket.round.php', + // FIXME following scopes differ from TM + 'meta.class.php', + ]) + expect(editor).toHaveScopesAtPosition([1, 25], 'true', ['source.php', 'meta.attribute.php', 'constant.language.php', + // FIXME following scopes differ from TM + 'meta.class.php', + ]) + expect(editor).toHaveScopesAtPosition([1, 29], ',', ['source.php', 'meta.attribute.php', 'punctuation.separator.delimiter.php', + // FIXME following scopes differ from TM + 'meta.class.php', + ]) + expect(editor).toHaveScopesAtPosition([1, 31], '2', ['source.php', 'meta.attribute.php', 'constant.numeric.decimal.php', + // FIXME following scopes differ from TM + 'meta.class.php', + ]) + expect(editor).toHaveScopesAtPosition([1, 32], ',', ['source.php', 'meta.attribute.php', 'punctuation.separator.delimiter.php', + // FIXME following scopes differ from TM + 'meta.class.php', + ]) + expect(editor).toHaveScopesAtPosition([1, 34], '[', ['source.php', 'meta.attribute.php', 'punctuation.section.array.begin.php', + // FIXME following scopes differ from TM + 'meta.class.php', + 'meta.array.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 35], '3', ['source.php', 'meta.attribute.php', 'constant.numeric.decimal.php', + // FIXME following scopes differ from TM + 'meta.class.php', + 'meta.array.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 36], '.', ['source.php', 'meta.attribute.php', 'constant.numeric.decimal.php', + // FIXME following scopes differ from TM + // 'punctuation.separator.decimal.period.php', + 'meta.class.php', + 'meta.array.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 37], '1', ['source.php', 'meta.attribute.php', 'constant.numeric.decimal.php', + // FIXME following scopes differ from TM + 'meta.class.php', + 'meta.array.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 38], ',', ['source.php', 'meta.attribute.php', 'punctuation.separator.delimiter.php', + // FIXME following scopes differ from TM + 'meta.class.php', + 'meta.array.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 40], '3', ['source.php', 'meta.attribute.php', 'constant.numeric.decimal.php', + // FIXME following scopes differ from TM + 'meta.class.php', + 'meta.array.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 41], '.', ['source.php', 'meta.attribute.php', 'constant.numeric.decimal.php', + // FIXME following scopes differ from TM + // 'punctuation.separator.decimal.period.php', + 'meta.class.php', + 'meta.array.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 42], '2', ['source.php', 'meta.attribute.php', 'constant.numeric.decimal.php', + // FIXME following scopes differ from TM + 'meta.class.php', + 'meta.array.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 43], ']', ['source.php', 'meta.attribute.php', 'punctuation.section.array.end.php', + // FIXME following scopes differ from TM + 'meta.class.php', + 'meta.array.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 44], ')', ['source.php', 'meta.attribute.php', 'punctuation.definition.arguments.end.bracket.round.php', + // FIXME following scopes differ from TM + 'meta.class.php', + ]) + expect(editor).toHaveScopesAtPosition([1, 45], ']', ['source.php', 'meta.attribute.php', + // FIXME following scopes differ from TM + 'meta.class.php', + ]) + }) + + // SKIP TS-php handles this sort of whitespace parsing + // it('should tokenize multiline attribute', () => {}) + + it('should tokenize attribute in anonymous class', () => { + editor.setPhpText('$foo = new #[ExampleAttribute] class {};') + + expect(editor).toHaveScopesAtPosition([1, 0], '$', ['source.php', 'variable.other.php', 'punctuation.definition.variable.php']) + expect(editor).toHaveScopesAtPosition([1, 1], 'foo', ['source.php', 'variable.other.php']) + expect(editor).toHaveScopesAtPosition([1, 5], '=', ['source.php', 'keyword.operator.assignment.php']) + expect(editor).toHaveScopesAtPosition([1, 7], 'new', ['source.php', 'meta.class.php', 'keyword.other.new.php']) + expect(editor).toHaveScopesAtPosition([1, 11], '#[', ['source.php', 'meta.class.php', 'meta.attribute.php']) + expect(editor).toHaveScopesAtPosition([1, 13], 'ExampleAttribute', ['source.php', 'meta.class.php', 'meta.attribute.php', 'support.attribute.php']) + expect(editor).toHaveScopesAtPosition([1, 29], ']', ['source.php', 'meta.class.php', 'meta.attribute.php']) + expect(editor).toHaveScopesAtPosition([1, 31], 'class', ['source.php', 'meta.class.php', 'storage.type.class.php']) + expect(editor).toHaveScopesAtPosition([1, 37], '{', ['source.php', 'meta.class.php', 'punctuation.definition.class.begin.bracket.curly.php', + // FIXME following scopes differ from TM + 'meta.class.body.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 38], '}', ['source.php', 'meta.class.php', 'punctuation.definition.class.end.bracket.curly.php', + // FIXME following scopes differ from TM + 'meta.class.body.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 39], ';', ['source.php', 'punctuation.terminator.expression.php']) + }) + + it('should tokenize attribute in arrow function', () => { + editor.setPhpText('$foo = #[ExampleAttribute] fn($x) => $x;') + + expect(editor).toHaveScopesAtPosition([1, 0], '$', ['source.php', 'variable.other.php', 'punctuation.definition.variable.php']) + expect(editor).toHaveScopesAtPosition([1, 1], 'foo', ['source.php', 'variable.other.php']) + expect(editor).toHaveScopesAtPosition([1, 5], '=', ['source.php', 'keyword.operator.assignment.php']) + expect(editor).toHaveScopesAtPosition([1, 7], '#[', ['source.php', 'meta.attribute.php', + // FIXME following scopes differ from TM + 'meta.function.closure.php', + ]) + expect(editor).toHaveScopesAtPosition([1, 9], 'ExampleAttribute', ['source.php', 'meta.attribute.php', 'support.attribute.php', + // FIXME following scopes differ from TM + 'meta.function.closure.php', + ]) + expect(editor).toHaveScopesAtPosition([1, 25], ']', ['source.php', 'meta.attribute.php', + // FIXME following scopes differ from TM + 'meta.function.closure.php', + ]) + expect(editor).toHaveScopesAtPosition([1, 27], 'fn', ['source.php', 'meta.function.closure.php', 'storage.type.function.php']) + expect(editor).toHaveScopesAtPosition([1, 29], '(', ['source.php', 'meta.function.closure.php', 'punctuation.definition.parameters.begin.bracket.round.php', + // FIXME following scopes differ from TM + 'meta.function.parameters.php', + ]) + expect(editor).toHaveScopesAtPosition([1, 30], '$', ['source.php', 'meta.function.closure.php', 'meta.function.parameters.php', 'meta.function.parameter.no-default.php', 'variable.other.php', 'punctuation.definition.variable.php']) + expect(editor).toHaveScopesAtPosition([1, 31], 'x', ['source.php', 'meta.function.closure.php', 'meta.function.parameters.php', 'meta.function.parameter.no-default.php', 'variable.other.php']) + expect(editor).toHaveScopesAtPosition([1, 32], ')', ['source.php', 'meta.function.closure.php', 'punctuation.definition.parameters.end.bracket.round.php', + // FIXME following scopes differ from TM + 'meta.function.parameters.php', + ]) + expect(editor).toHaveScopesAtPosition([1, 34], '=>', ['source.php', 'meta.function.closure.php', 'punctuation.definition.arrow.php']) + expect(editor).toHaveScopesAtPosition([1, 37], '$', ['source.php', 'variable.other.php', 'punctuation.definition.variable.php', + // FIXME following scopes differ from TM + 'meta.function.closure.php', + ]) + expect(editor).toHaveScopesAtPosition([1, 38], 'x', ['source.php', 'variable.other.php', + // FIXME following scopes differ from TM + 'meta.function.closure.php', + ]) + expect(editor).toHaveScopesAtPosition([1, 39], ';', ['source.php', 'punctuation.terminator.expression.php']) + }) + + it('should tokenize builtin attribute', () => { + editor.setPhpText(` + #[Attribute(Attribute::TARGET_CLASS)] + class FooAttribute {} + `) + + expect(editor).toHaveScopesAtPosition([1, 0], '#[', ['source.php', 'meta.attribute.php', + // FIXME following scopes differ from TM + 'meta.class.php', + ]) + expect(editor).toHaveScopesAtPosition([1, 2], 'Attribute', ['source.php', 'meta.attribute.php', 'support.attribute.builtin.php', + // FIXME following scopes differ from TM + 'meta.class.php', + ]) + expect(editor).toHaveScopesAtPosition([1, 11], '(', ['source.php', 'meta.attribute.php', 'punctuation.definition.arguments.begin.bracket.round.php', + // FIXME following scopes differ from TM + 'meta.class.php', + ]) + expect(editor).toHaveScopesAtPosition([1, 12], 'Attribute', ['source.php', 'meta.attribute.php', 'support.class.builtin.php', + // FIXME following scopes differ from TM + 'meta.class.php', + ]) + expect(editor).toHaveScopesAtPosition([1, 21], '::', ['source.php', 'meta.attribute.php', 'keyword.operator.class.php', + // FIXME following scopes differ from TM + 'meta.class.php', + ]) + expect(editor).toHaveScopesAtPosition([1, 23], 'TARGET_CLASS', ['source.php', 'meta.attribute.php', 'constant.other.class.php', + // FIXME following scopes differ from TM + 'meta.class.php', + ]) + expect(editor).toHaveScopesAtPosition([1, 35], ')', ['source.php', 'meta.attribute.php', 'punctuation.definition.arguments.end.bracket.round.php', + // FIXME following scopes differ from TM + 'meta.class.php', + ]) + expect(editor).toHaveScopesAtPosition([1, 36], ']', ['source.php', 'meta.attribute.php', + // FIXME following scopes differ from TM + 'meta.class.php', + ]) + expect(editor).toHaveScopesAtPosition([2, 0], 'class', ['source.php', 'meta.class.php', 'storage.type.class.php', + // FIXME following scopes differ from TM + 'meta.class.php', + ]) + expect(editor).toHaveScopesAtPosition([2, 6], 'FooAttribute', ['source.php', 'meta.class.php', 'entity.name.type.class.php', + // FIXME following scopes differ from TM + 'meta.class.php', + ]) + }) + }) + + // FIXME following scopes differ from TM + // they all include 'meta.embedded.block.php', but TM does not + describe("PHPDoc", () => { + it("should tokenize tags correctly", () => { + editor.setPhpText(` + /** + * @api + */ + `) + + expectTokensToEqual( + editor, + [ + [ + {text: '/**', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php']}, + ], + [ + {text: ' ', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'leading-whitespace']}, + {text: '* ', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php']}, + {text: '@api', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'keyword.other.phpdoc.php']}, + ], + [ + {text: ' ', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'leading-whitespace']}, + {text: '*/', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php']}, + ], + ] + ) + }); + + it("should tokenize single line phpdoc correctly", () => { + editor.setPhpText(`/** @api */`) + + expectTokensToEqual( + editor, + [ + [ + {text: '/** ', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php']}, + {text: '@api', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'keyword.other.phpdoc.php']}, + {text: ' */', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php']}, + ], + ] + ) + }); + + it("should tokenize inline phpdoc correctly", () => { + editor.setPhpText(`/** {@inheritDoc} */`) + + expectTokensToEqual( + editor, + [ + [ + {text: '/** {', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php']}, + {text: '@inheritDoc', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'keyword.other.phpdoc.php']}, + {text: '} */', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php']}, + ], + ] + ) + }); + + describe('types', () => { + it('should tokenize a primitive type', () => { + editor.setPhpText(` + /** + * @param int $foo description + */ + `); + + expectTokensToEqual( + editor, + [ + [ + {text: '/**', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php']}, + ], + [ + {text: ' ', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'leading-whitespace']}, + {text: '* ', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php']}, + {text: '@param', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'keyword.other.phpdoc.php']}, + {text: ' ', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php']}, + {text: 'int', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'keyword.other.type.php']}, + {text: ' $foo description', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php']}, + ], + [ + {text: ' ', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'leading-whitespace']}, + {text: '*/', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php']}, + ] + ]); + }); + + it('should tokenize a named type', () => { + editor.setPhpText(` + /** + * @param Test $foo description + */ + `); + + expectTokensToEqual( + editor, + [ + [ + {text: '/**', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php']}, + ], + [ + {text: ' ', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'leading-whitespace']}, + {text: '* ', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php']}, + {text: '@param', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'keyword.other.phpdoc.php']}, + {text: ' ', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php']}, + {text: 'Test', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'support.class.php']}, + {text: ' $foo description', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php']}, + ], + [ + {text: ' ', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'leading-whitespace']}, + {text: '*/', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php']}, + ] + ] + ); + }); + + it('should tokenize a named type in @method tags', () => { + editor.setPhpText(` + /** + * @method Foo\\Bar name(Fizz\\Buzz $foo) + */ + `); + + expectTokensToEqual( + editor, + [ + [ + {text: '/**', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php']}, + ], + [ + {text: ' ', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'leading-whitespace']}, + {text: '* ', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php']}, + {text: '@method', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'keyword.other.phpdoc.php']}, + {text: ' ', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php']}, + // FIXME following scopes differ from TM + {text: 'Foo\\', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'support.other.namespace.php'] }, + {text: 'Bar', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'support.class.php'] }, + {text: ' name(', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php']}, + {text: 'Fizz\\', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'support.other.namespace.php'] }, + {text: 'Buzz', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'support.class.php'] }, + {text: ' $foo)', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php']}, + ], + [ + {text: ' ', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'leading-whitespace']}, + {text: '*/', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php']}, + ] + ] + ); + }); + + it('should tokenize a single namespaced type', () => { + editor.setPhpText(` + /** + * @param \\Test\\Type $foo description + */ + `); + ; + + expectTokensToEqual( + editor, + [ + [ + {text: '/**', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php']}, + ], + [ + {text: '* ', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php']}, + {text: '@param', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'keyword.other.phpdoc.php']}, + {text: ' ', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php']}, + // FIXME following scopes differ from TM + {text: '\\Test\\', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'support.other.namespace.php'] }, + // {text: '\\', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'support.other.namespace.php', 'punctuation.separator.inheritance.php'] }, + // {text: 'Test', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'support.other.namespace.php'] }, + // {text: '\\', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'support.other.namespace.php', 'punctuation.separator.inheritance.php'] }, + {text: 'Type', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'support.class.php'] }, + {text: ' $foo description', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php']}, + ], + [ + {text: '*/', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php']}, + ] + ] + ); + }); + + it('should tokenize multiple types', () => { + editor.setPhpText(` + /** + * @param int|Class $foo description + */ + `); + ; + + expectTokensToEqual( + editor, + [ + [ + {text: '/**', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php']}, + ], + [ + {text: ' ', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'leading-whitespace']}, + {text: '* ', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php']}, + {text: '@param', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'keyword.other.phpdoc.php'] }, + {text: ' ', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php'] }, + {text: 'int', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'keyword.other.type.php'] }, + {text: '|', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'punctuation.separator.delimiter.php'] }, + {text: 'Class', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'support.class.php'] }, + {text: ' $foo description', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php']}, + ], + [ + {text: ' ', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'leading-whitespace']}, + {text: '*/', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php']}, + ] + ] + ) + }); + + it('should tokenize multiple namespaced types', () => { + editor.setPhpText(` + /** + * @param Test\\One|\\Another\\Root $foo description + */ + `); + + expectTokensToEqual( + editor, + [ + [ + {text: '/**', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php']}, + ], + [ + {text: ' ', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'leading-whitespace']}, + {text: '* ', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php']}, + {text: '@param', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'keyword.other.phpdoc.php'] }, + {text: ' ', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php'] }, + // FIXME following scopes differ from TM + {text: 'Test\\', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'support.other.namespace.php'] }, + // {text: 'Test', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'support.other.namespace.php'] }, + // {text: '\\', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'support.other.namespace.php', 'punctuation.separator.inheritance.php'] }, + {text: 'One', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'support.class.php'] }, + {text: '|', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'punctuation.separator.delimiter.php'] }, + // FIXME following scopes differ from TM + {text: '\\Another\\', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'support.other.namespace.php'] }, + // {text: '\\', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'support.other.namespace.php', 'punctuation.separator.inheritance.php'] }, + // {text: 'Another', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'support.other.namespace.php'] }, + // {text: '\\', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'support.other.namespace.php', 'punctuation.separator.inheritance.php'] }, + {text: 'Root', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'support.class.php'] }, + {text: ' $foo description', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php'] }, + ], + [ + {text: ' ', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'leading-whitespace']}, + {text: '*/', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php']}, + ] + ] + ) + }); + + it('should tokenize a single primitive array type', () => { + editor.setPhpText(`/** @param int[] $foo */`); + + expectTokensToEqual( + editor, + [ + [ + {text: '/** ', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php']}, + {text: '@param', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'keyword.other.phpdoc.php'] }, + {text: ' ', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php'] }, + {text: 'int', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'keyword.other.type.php'] }, + {text: '[]', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'keyword.other.array.phpdoc.php'] }, + {text: ' $foo */', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php'] }, + ] + ] + ) + }) + + it('should tokenize a single named array type', () => { + editor.setPhpText(`/** @param Test[] $foo */`); + + expectTokensToEqual( + editor, + [ + [ + {text: '/** ', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php']}, + {text: '@param', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'keyword.other.phpdoc.php'] }, + {text: ' ', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php'] }, + {text: 'Test', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'support.class.php'] }, + {text: '[]', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'keyword.other.array.phpdoc.php'] }, + {text: ' $foo */', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php'] }, + ] + ] + ) + }); + + it('should tokenize a single namespaced array type', () => { + editor.setPhpText(`/** @param Test\\Type[] $foo */`); + + expectTokensToEqual( + editor, + [ + [ + {text: '/** ', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php']}, + {text: '@param', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'keyword.other.phpdoc.php'] }, + {text: ' ', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php'] }, + // FIXME following scopes differ from TM + {text: 'Test\\', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'support.other.namespace.php'] }, + // {text: 'Test', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'support.other.namespace.php'] }, + // {text: '\\', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'support.other.namespace.php', 'punctuation.separator.inheritance.php'] }, + {text: 'Type', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'support.class.php'] }, + {text: '[]', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'keyword.other.array.phpdoc.php'] }, + {text: ' $foo */', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php'] }, + ] + ] + ) + }); + + // FIXME multiple/grouped types are not currently supported by TS-phpdoc + xit('should tokenize multiple array types', () => { + editor.setPhpText(`/** @param (int|Class)[] $foo */`); + + expectTokensToEqual( + editor, + [ + [ + {text: '/** ', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php']}, + {text: '@param', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'keyword.other.phpdoc.php'] }, + {text: ' ', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php'] }, + {text: '(', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'punctuation.definition.type.begin.bracket.round.phpdoc.php'] }, + {text: 'int', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'keyword.other.type.php'] }, + {text: '|', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'punctuation.separator.delimiter.php'] }, + {text: 'Class', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'support.class.php'] }, + {text: ')', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'punctuation.definition.type.end.bracket.round.phpdoc.php'] }, + {text: '[]', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'keyword.other.array.phpdoc.php'] }, + ] + ] + ) + }); + + // FIXME multiple/grouped types are not currently supported by TS-phpdoc + xit('should tokenize complicated multiple array types', () => { + editor.setPhpText(`/** @param ((Test|int)[]|Test\\Type[]|string[]|resource)[] $foo`); + + expectTokensToEqual( + editor, + [ + [ + {text: '@param', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'keyword.other.phpdoc.php'] }, + {text: ' ', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php'] }, + {text: '(', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'punctuation.definition.type.begin.bracket.round.phpdoc.php'] }, + {text: '(', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'punctuation.definition.type.begin.bracket.round.phpdoc.php'] }, + {text: 'Test', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'support.class.php'] }, + {text: '|', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'punctuation.separator.delimiter.php'] }, + {text: 'int', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'keyword.other.type.php'] }, + {text: ')', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'punctuation.definition.type.end.bracket.round.phpdoc.php'] }, + {text: '[]', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'keyword.other.array.phpdoc.php'] }, + {text: '|', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'punctuation.separator.delimiter.php'] }, + {text: 'Test', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'support.other.namespace.php'] }, + {text: '\\', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'support.other.namespace.php', 'punctuation.separator.inheritance.php'] }, + {text: 'Type', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'support.class.php'] }, + {text: '[]', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'keyword.other.array.phpdoc.php'] }, + {text: '|', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'punctuation.separator.delimiter.php'] }, + {text: 'string', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'keyword.other.type.php'] }, + {text: '[]', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'keyword.other.array.phpdoc.php'] }, + {text: '|', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'punctuation.separator.delimiter.php'] }, + {text: 'resource', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'keyword.other.type.php'] }, + {text: ')', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'punctuation.definition.type.end.bracket.round.phpdoc.php'] }, + {text: '[]', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php', 'meta.other.type.phpdoc.php', 'keyword.other.array.phpdoc.php'] }, + {text: ' description', scopes: ['meta.embedded.block.php', 'source.php', 'comment.block.documentation.phpdoc.php'] }, + ] + ] + ) + }); + }); + + }) + + describe("strings", () => { + it('scopes single quoted strings', () => { + editor.setPhpText("'just a string'") + + expect(editor).toHaveScopesAtPosition([1, 0], '\'', ["source.php", "string.quoted.single.php", + // FIXME following scopes differ from TM + // "punctuation.definition.string.begin.php" + ]) + expect(editor).toHaveScopesAtPosition([1, 1], 'just', ["source.php", "string.quoted.single.php"]) + expect(editor).toHaveScopesAtPosition([1, 14], '\'', ["source.php", "string.quoted.single.php", + // FIXME following scopes differ from TM + // "punctuation.definition.string.end.php" + ]) + }) + + it('scopes double quoted strings', () => { + editor.setPhpText('"just a string";') + + expect(editor).toHaveScopesAtPosition([1, 0], '"', ["source.php", "string.quoted.double.php", + // FIXME following scopes differ from TM + // "punctuation.definition.string.begin.php" + ]) + expect(editor).toHaveScopesAtPosition([1, 1], 'just', ["source.php", "string.quoted.double.php"]) + expect(editor).toHaveScopesAtPosition([1, 14], '"', ["source.php", "string.quoted.double.php", + // FIXME following scopes differ from TM + // "punctuation.definition.string.end.php" + ]) + }) + + it('handles strings containing php tags (regression)', () => { + // this regression was in the embedded-php parser, not this grammar + + editor.setPhpText('"just a string ?>" . $foo;') + + expect(editor).toHaveScopesAtPosition([1, 0], '"', ["source.php", "string.quoted.double.php", + // FIXME following scopes differ from TM + // "punctuation.definition.string.begin.php" + ]) + expect(editor).toHaveScopesAtPosition([1, 1], 'just', ["source.php", "string.quoted.double.php"]) + expect(editor).toHaveScopesAtPosition([1, 15], '?>', ["source.php", "string.quoted.double.php", + // FIXME following scopes differ from TM + // "punctuation.definition.string.end.php" + ]) + expect(editor).toHaveScopesAtPosition([1, 17], '"', ["source.php", "string.quoted.double.php", + // FIXME following scopes differ from TM + // "punctuation.definition.string.end.php" + ]) + expect(editor).toHaveScopesAtPosition([1, 19], '.', ["source.php", "keyword.operator.string.php"]) + expect(editor).toHaveScopesAtPosition([1, 21], '$', ['source.php', 'variable.other.php', 'punctuation.definition.variable.php']) + }) + + describe('string escape sequences', () => { + it( 'tokenizes escaped octal sequences', () => { + editor.setPhpText('"test \\007 test";') + + expect(editor).toHaveScopesAtPosition([1, 0], '"', ['source.php', 'string.quoted.double.php', + // FIXME following scopes differ from TM + // 'punctuation.definition.string.begin.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 1], 'test ', ['source.php', 'string.quoted.double.php']) + expect(editor).toHaveScopesAtPosition([1, 6], '\\007', ['source.php', 'string.quoted.double.php', 'constant.character.escape.octal.php']) + expect(editor).toHaveScopesAtPosition([1, 10], ' test', ['source.php', 'string.quoted.double.php']) + expect(editor).toHaveScopesAtPosition([1, 15], '"', ['source.php', 'string.quoted.double.php', + // FIXME following scopes differ from TM + // 'punctuation.definition.string.end.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 16], ';', ['source.php', 'punctuation.terminator.expression.php']) + }) + + it( 'tokenizes escaped hex sequences', () => { + editor.setPhpText('"test \\x0f test";') + + expect(editor).toHaveScopesAtPosition([1, 0], '"', ['source.php', 'string.quoted.double.php', + // FIXME following scopes differ from TM + // 'punctuation.definition.string.begin.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 1], 'test ', ['source.php', 'string.quoted.double.php']) + expect(editor).toHaveScopesAtPosition([1, 6], '\\x0f', ['source.php', 'string.quoted.double.php', 'constant.character.escape.hex.php']) + expect(editor).toHaveScopesAtPosition([1, 10], ' test', ['source.php', 'string.quoted.double.php']) + expect(editor).toHaveScopesAtPosition([1, 15], '"', ['source.php', 'string.quoted.double.php', + // FIXME following scopes differ from TM + // 'punctuation.definition.string.end.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 16], ';', ['source.php', 'punctuation.terminator.expression.php']) + }) + + it( 'tokenizes escaped unicode sequences', () => { + editor.setPhpText('"test \\u{00A0} test";') + + expect(editor).toHaveScopesAtPosition([1, 0], '"', ['source.php', 'string.quoted.double.php', + // FIXME following scopes differ from TM + // 'punctuation.definition.string.begin.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 1], 'test ', ['source.php', 'string.quoted.double.php']) + expect(editor).toHaveScopesAtPosition([1, 6], '\\u{00A0}', ['source.php', 'string.quoted.double.php', 'constant.character.escape.unicode.php']) + expect(editor).toHaveScopesAtPosition([1, 14], ' test', ['source.php', 'string.quoted.double.php']) + expect(editor).toHaveScopesAtPosition([1, 19], '"', ['source.php', 'string.quoted.double.php', + // FIXME following scopes differ from TM + // 'punctuation.definition.string.end.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 20], ';', ['source.php', 'punctuation.terminator.expression.php']) + }) + + // FIXME how to test \n as an escape sequence? it's tested in the TM + // tests, but not working here + for (let escapeCharacter of ['r', 't', 'v', 'e', 'f', '$', '"', '\\']) { + it(`tokenizes ${escapeCharacter} as an escape character`, () => { + editor.setPhpText(`"test \\${escapeCharacter} test";`) + + expect(editor).toHaveScopesAtPosition([1, 0], '"', ['source.php', 'string.quoted.double.php', + // FIXME following scopes differ from TM + // 'punctuation.definition.string.begin.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 1], 'test ', ['source.php', 'string.quoted.double.php']) + expect(editor).toHaveScopesAtPosition([1, 6], `${escapeCharacter}`, ['source.php', 'string.quoted.double.php', 'constant.character.escape.php']) + expect(editor).toHaveScopesAtPosition([1, 8], ' test', ['source.php', 'string.quoted.double.php']) + expect(editor).toHaveScopesAtPosition([1, 13], '"', ['source.php', 'string.quoted.double.php', + // FIXME following scopes differ from TM + // 'punctuation.definition.string.end.php' + ]) + expect(editor).toHaveScopesAtPosition([1, 14], ';', ['source.php', 'punctuation.terminator.expression.php']) + }) + } + }) + + describe("heredoc", () => { + it("should tokenize a simple heredoc correctly", () => { + editor.setPhpText(` + << { + it("should tokenize a simple nowdoc correctly", () => { + editor.setPhpText(` + <<<'NOWDOC' + I am a nowdoc + NOWDOC; + `); + + expect(editor).toHaveScopesAtPosition([1, 0], '<<<', ["source.php", "string.unquoted.nowdoc.php", + // FIXME following scopes differ from TM + // 'punctuation.definition.string.php' + ]); + expect(editor).toHaveScopesAtPosition([1, 3], '\'', ['source.php', "string.unquoted.nowdoc.php"]); + expect(editor).toHaveScopesAtPosition([1, 4], 'NOWDOC', ["source.php", "string.unquoted.nowdoc.php", + // FIXME following scopes differ from TM + // 'keyword.operator.nowdoc.php' + ]); + expect(editor).toHaveScopesAtPosition([1, 10], '\'', ['source.php', "string.unquoted.nowdoc.php"]); + expect(editor).toHaveScopesAtPosition([2, 0], 'I am a nowdoc', ['source.php', "string.unquoted.nowdoc.php"]); + expect(editor).toHaveScopesAtPosition([3, 0], 'NOWDOC', ["source.php", "string.unquoted.nowdoc.php", + // FIXME following scopes differ from TM + // 'keyword.operator.nowdoc.php' + ]); + expect(editor).toHaveScopesAtPosition([3, 7], ';', ['source.php', "punctuation.terminator.expression.php"]); + }); + }); + }); +}); From 9505029fc3a65d209591eaad04fdf095a8592dfd Mon Sep 17 00:00:00 2001 From: Clayton Carter Date: Wed, 27 Apr 2022 20:40:32 -0400 Subject: [PATCH 20/21] Update the core tree-sitter grammar --- grammars/tree-sitter-php.cson | 461 ++++++++++++++++++++++++---------- 1 file changed, 329 insertions(+), 132 deletions(-) diff --git a/grammars/tree-sitter-php.cson b/grammars/tree-sitter-php.cson index 48810893..4c2a7c3b 100644 --- a/grammars/tree-sitter-php.cson +++ b/grammars/tree-sitter-php.cson @@ -1,19 +1,12 @@ name: 'PHP' -scopeName: 'text.html.php' +scopeName: 'source.php' type: 'tree-sitter' -parser: 'tree-sitter-php' +parser: 'tree-sitter-php-abc' -firstLineRegex: [ - '^\\s*<\\?([pP][hH][pP]|=|\\s|$)' -] +injectionRegex: 'php|PHP' comments: - start: '#' - -fileTypes: [ - 'php' - 'phpt' -] + start: '//' folds: [ { @@ -21,160 +14,328 @@ folds: [ } { start: {type: '{', index: 0}, - end: {'}', index: -1} - } - { - type: ['class_declaration', 'interface_declaration'] - start: {type: '{'}, - end: {type: '}'} - } - { - type: 'formal_parameters' - start: {type: '('} - end: {type: ')'} + end: {type: '}', index: -1} } { start: {type: '(', index: 0}, - end: {')', index: -1} + end: {type: ')', index: -1} } { start: {type: '[', index: 0}, - end: {']', index: -1} + end: {type: ']', index: -1} } ] scopes: 'program': 'source.php' - '"echo"': 'support.function' - '"print"': 'support.function' - '"unset"': 'support.function' - '"isset"': 'support.function' - '"list"': 'support.function' - '"eval"': 'support.function' - '"array"': 'support.function' - '"list"': 'support.function' - '"empty"': 'support.function' - 'string': 'string.quoted' - 'float': 'constant.numeric' + 'php_tag': 'punctuation.section.embedded.begin.php' + '"?>"': 'punctuation.section.embedded.end.php' + + 'variable_name': [ + {exact: '$this', scopes: 'variable.language.this.php'}, + 'variable.other.php' + ] + 'variable_name > "$"': 'punctuation.definition.variable.php' + + 'name': [ + {match: 'ArrayObject', scopes: 'support.class.builtin.php'}, + 'support.class.php' + ] + 'variable_name > name': '' + + 'class_declaration': 'meta.class.php' + 'class_declaration > name': 'entity.name.type.class.php' + 'base_clause > ","': 'punctuation.separator.classes.php' + 'base_clause > name': 'entity.other.inherited-class.php' + 'base_clause > qualified_name > name': 'entity.other.inherited-class.php' + 'class_interface_clause > name': 'entity.name.type.interface.php' + 'class_declaration > declaration_list > "{"': 'punctuation.definition.class.begin.bracket.curly.php' + 'class_declaration > declaration_list > "}"': 'punctuation.definition.class.end.bracket.curly.php' + 'class_declaration > declaration_list': 'meta.class.body.php' + + 'interface_declaration': 'meta.interface.php' + 'interface_declaration > name': 'entity.name.type.interface.php' + + 'trait_declaration': 'meta.trait.php' + 'trait_declaration > name': 'entity.name.type.trait.php' + + 'use_declaration': 'meta.use.php' + 'use_instead_of_clause > "insteadof"': 'keyword.other.use-insteadof.php' + 'use_list > "{"': 'punctuation.definition.use.begin.bracket.curly.php' + 'use_list > "}"': 'punctuation.definition.use.end.bracket.curly.php' + 'use_as_clause > name:nth-child(0)': 'support.class.php' + 'use_as_clause > "as"': 'keyword.other.use-as.php' + 'use_as_clause > name': 'entity.other.alias.php' + + 'object_creation_expression': {match: '\\bclass\\b', scopes: 'meta.class.php'} + 'object_creation_expression > declaration_list': 'meta.class.body.php' + + 'function_definition': 'meta.function.php' + 'function_definition > name': 'entity.name.function.php' + 'function_definition > ":"': 'keyword.operator.return-value.php' + + 'anonymous_function_creation_expression': 'meta.function.closure.php' + 'anonymous_function_creation_expression > ":"': 'keyword.operator.return-value.php' + 'anonymous_function_use_clause': 'meta.function.closure.use.php' + 'anonymous_function_use_clause > "use"': 'keyword.other.function.use.php' + + 'reference_modifier > "&"': 'storage.modifier.reference.php' + 'by_ref > "&"': 'storage.modifier.reference.php' + + 'arrow_function': 'meta.function.closure.php' + 'arrow_function > "=>"': 'punctuation.definition.arrow.php' + 'arrow_function > ":"': 'keyword.operator.return-value.php' + + 'method_declaration': 'meta.function.php' + 'method_declaration > ":"': 'keyword.operator.return-value.php' + 'method_declaration > name': [ + # FIXME other special/magic methods? + {exact: '__construct', scopes: 'support.function.constructor.php'}, + {match: '__(call|callStatic|destruct|get|set|isset|unset|sleep|wakeup|serialize|unserialize|toString|invoke|set_state|clone|debugInfo)', scopes: 'support.function.magic.php'}, + 'entity.name.function.php' + ] + + # WTF only echo statements w/ parens are scoped as "function calls", but we + # can't select for echo_statements that contain parenthesized_expression, so + # ... regex it is + 'echo_statement': { match: '^echo\\s*\\(', scopes: 'meta.function-call.php'} + 'echo_statement > "echo"': 'support.function.construct.output.php' + 'unset_statement > "unset"': 'support.function.php' + + 'function_call_expression': [ + {match: '^\\$', scopes: 'meta.function-call.invoke.php'} + 'meta.function-call.php' + ] + 'function_call_expression > qualified_name > name': 'entity.name.function.php' + + 'member_call_expression': 'meta.method-call.php' + 'nullsafe_member_call_expression': 'meta.method-call.php' + 'scoped_call_expression': 'meta.method-call.static.php' + ''' + member_call_expression > name, + nullsafe_member_call_expression > name, + scoped_call_expression > name + ''': 'entity.name.function.php' + 'nullsafe_member_call_expression > "?->"': 'keyword.operator.class.php' + 'scoped_call_expression > name:nth-child(0)': 'support.class.php' + 'scoped_call_expression > relative_scope': {match: /(parent|static|self)/i, scopes: 'storage.type.php'} + + 'argument > name': 'entity.name.variable.parameter.php' + 'argument > ":"': 'punctuation.separator.colon.php' + + 'formal_parameters': 'meta.function.parameters.php' + 'formal_parameters > simple_parameter': [ + {match: '=', scopes:'meta.function.parameter.default.php'}, + 'meta.function.parameter.no-default.php' + ] + 'formal_parameters > variadic_parameter': 'meta.function.parameter.variadic.php' + 'formal_parameters > variadic_parameter > "..."': 'keyword.operator.variadic.php' + + 'property_promotion_parameter': 'meta.function.parameter.promoted-property.php' + + 'primitive_type': [ + {exact: 'static', scopes: 'storage.type.php'}, + 'keyword.other.type.php' + ] + 'optional_type > "?"': 'keyword.operator.nullable-type.php' + '* > named_type > name': [ + {exact: 'self', scopes: 'storage.type.php'}, + # FIXME need to support ALL of the types, I suppose... + {match: 'Array', scopes: 'keyword.other.type.php'}, + 'support.class.php' + ] + 'type_list > "|"' : 'punctuation.separator.delimiter.php' + # FIXME TS-php doesn't currently support php8.1 intersection types. When it + # does, this will start working + 'type_list > "&"' : 'punctuation.separator.delimiter.php' 'member_access_expression > name': 'variable.other.object.property' + 'scoped_property_access_expression > variable_name': 'variable.other.class.php' + 'class_constant_access_expression > name:nth-child(0)': [ + # FIXME there are lots of these + {exact: 'Attribute', scopes: 'support.class.builtin.php'}, + 'support.class.php' + ] + 'class_constant_access_expression > name': [ + {exact: 'class', scopes: 'keyword.other.class.php'}, + 'constant.other.class.php' + ] - 'class_declaration > name': 'entity.name.type.class' - 'catch_clause > qualified_name > name': 'entity.name.type.class' - 'class_base_clause > qualified_name > name': 'entity.other.inherited-class' + # FIXME we need to select "\", but this appears to be broken in Atom's selector implementation + # 'namespace_name > "\\"': 'punctuation.separator.inheritance.php' + # HACK + 'namespace_name > name': '' + '* > namespace_name_as_prefix': 'support.other.namespace.php' - 'qualified_name > name': {match: /^(TRUE|FALSE|NULL|__(FILE|DIR|FUNCTION|CLASS|METHOD|LINE|NAMESPACE)__|ON|OFF|YES|NO|NL|BR|TAB)$/i, scopes: 'constant.language.php'} + 'namespace_definition': 'meta.namespace.php' + 'namespace_definition > namespace_name': 'entity.name.type.namespace.php' + 'namespace_definition > compound_statement > "{"': 'punctuation.definition.namespace.begin.bracket.curly.php' + 'namespace_definition > compound_statement > "}"': 'punctuation.definition.namespace.end.bracket.curly.php' - 'interface_declaration > name': 'entity.name.type.interface' - 'class_interface_clause > qualified_name > name': 'entity.other.implemented-interface' + 'namespace_use_declaration': 'meta.use.php' + 'namespace_use_declaration > namespace_name': 'support.other.namespace.php' + 'namespace_use_declaration > "const"': 'storage.type.const.php' + 'namespace_use_group > "{"': 'punctuation.definition.use.begin.bracket.curly.php' + 'namespace_use_group > "}"': 'punctuation.definition.use.end.bracket.curly.php' + 'namespace_use_group_clause > namespace_name > name': 'support.class.php' + 'namespace_aliasing_clause > "as"': 'keyword.other.use-as.php' + 'namespace_aliasing_clause > name': 'entity.other.alias.php' - 'function_definition > name': 'entity.name.function' - 'function_call_expression > name': 'entity.name.function' - 'function_call_expression > qualified_name': 'entity.name.function' - 'scoped_call_expression > name': 'entity.name.function' - 'member_call_expression > name': 'entity.name.function' - '"require_once", "require", "include_once", "include"': 'entity.name.function' + 'attribute_list': 'meta.attribute.php' + 'attribute_list > attribute > name': [ + # FIXME are there more of these? + {exact: 'Attribute', scopes: 'support.attribute.builtin.php'} + 'support.attribute.php' + ] + # prevent the end bracket from being scoped as part of an array + 'attribute_list > "]"': '' - 'method_declaration > name': 'entity.name.function' - 'method_declaration > function_definition > name': 'entity.name.function' - 'constructor_definition > "__construct"': 'entity.name.function' + 'switch_statement': 'meta.switch-statement.php' + 'switch_statement > parenthesized_expression > "("': 'punctuation.definition.switch-expression.begin.bracket.round.php' + 'switch_statement > parenthesized_expression > ")"': 'punctuation.definition.switch-expression.end.bracket.round.php' + 'switch_block > "{"': 'punctuation.definition.section.switch-block.begin.bracket.curly.php' + 'switch_block > "}"': 'punctuation.definition.section.switch-block.end.bracket.curly.php' + 'case_statement > ":", default_statement > ":"': 'punctuation.terminator.statement.php' - 'object_creation_expression > qualified_name > name': 'entity.name.type.class' - 'object_creation_expression > new_variable > simple_variable': 'meta.class.instance.constructor' + 'match_expression': 'meta.match-statement.php' + 'match_expression > parenthesized_expression > "("': 'punctuation.definition.match-expression.begin.bracket.round.php' + 'match_expression > parenthesized_expression > ")"': 'punctuation.definition.match-expression.end.bracket.round.php' + 'match_conditional_expression > "=>"': 'keyword.definition.arrow.php' + 'match_default_expression > "=>"': 'keyword.definition.arrow.php' + 'match_block > "{"': 'punctuation.definition.section.match-block.begin.bracket.curly.php' + 'match_block > "}"': 'punctuation.definition.section.match-block.end.bracket.curly.php' + + 'catch_clause': 'meta.catch.php' + 'catch_clause > type_list > named_type > name': 'support.class.exception.php' + 'catch_clause > type_list > named_type > qualified_name > name': 'support.class.exception.php' 'const_element > name': 'constant' - 'class_constant_access_expression >name': 'constant' - 'integer': 'constant.numeric.decimal' - 'float': 'constant.numeric.decimal' - 'string': 'string.quoted.single' - 'regex': 'string.regexp' - 'true': 'constant.language.boolean.true' - 'false': 'constant.language.boolean.false' - 'comment': 'comment.block' + 'boolean, null': 'constant.language.php' + 'integer': [ + {match: '^0[xX]', scopes: 'constant.numeric.hex.php'}, + {match: '^0[bB]', scopes: 'constant.numeric.binary.php'}, + {match: '^0[oO0-7]', scopes: 'constant.numeric.octal.php'}, + 'constant.numeric.decimal.php' + ] + 'float': 'constant.numeric.decimal.php' + 'array_creation_expression': 'meta.array.php' + # FIXME this isn't working, how to scope the . in floats? + # NOTE the anonymous scope appears to be saved into the grammar scopeMap, but + # it's not being applied + # 'float > "."': 'punctuation.separator.decimal.period.php' - 'variable_name > "$"': 'punctuation.definition.variable.php' - '"("': 'punctuation.definition.parameters.begin.bracket.round.php' - '")"': 'punctuation.definition.parameters.end.bracket.round.php' + 'string': 'string.quoted.single.php' + 'encapsed_string': 'string.quoted.double.php' + 'encapsed_string > string': 'string.quoted.double.php' + 'encapsed_string > escape_sequence': [ + {match: '^\\\\[xX]', scopes: 'constant.character.escape.hex.php'}, + {match: '^\\\\[0-7]', scopes: 'constant.character.escape.octal.php'}, + {match: '^\\\\[uU]', scopes: 'constant.character.escape.unicode.php'}, + 'constant.character.escape.php' + ] + # FIXME this DOES work + # "'\"'": 'punctuation.definition.string.end.php' + # "'\"':nth-child(0)": 'punctuation.definition.string.begin.php' + # FIXME this does NOT work + # '"\'"': 'punctuation.definition.string.end.php' + # "'\'':nth-child(0)": 'punctuation.definition.string.begin.php' + # FIXME this DOES work + # "encapsed_string > '\"'": 'punctuation.definition.string.php' + # FIXME this doesn't work + # 'string > "\'"': 'punctuation.definition.string.begin.php' + # NOTE the following works in language-ruby, but TS-ruby scopes string content + # separately from the string punctuation + # "string > '\"':nth-child(0)": 'punctuation.definition.string.begin' + # "string > '\"':nth-child(1)": 'punctuation.definition.string.end' + + 'heredoc': [ + {match: "^[\r\n\s]*<<<'", scopes: 'string.unquoted.nowdoc.php'}, + 'string.unquoted.heredoc.php' + ] + + 'comment': [ + {match: '^/\\*\\*', scopes: 'comment.block.documentation.phpdoc.php'}, + {match: '^//', scopes: 'comment.line.double-slash.php'}, + {match: '^#', scopes: 'comment.line.number-sign.php'}, + 'comment.block.php' + ] + # FIXME these do NOT work + # 'comment > "//"': 'punctuation.definition.comment.php' + # 'comment > "/*"': 'punctuation.definition.comment.php' + # 'comment > "/**"': 'punctuation.definition.comment.php' + # 'comment > "*/"': 'punctuation.definition.comment.php' + # 'comment > "#"': 'punctuation.definition.comment.php' + + 'parenthesized_expression > "("': 'punctuation.definition.begin.bracket.round.php' + 'parenthesized_expression > ")"': 'punctuation.definition.end.bracket.round.php' + 'object_creation_expression > arguments > "("': 'punctuation.definition.begin.bracket.round.php' + 'object_creation_expression > arguments > ")"': 'punctuation.definition.end.bracket.round.php' + 'arguments > "("': 'punctuation.definition.arguments.begin.bracket.round.php' + 'arguments > ")"': 'punctuation.definition.arguments.end.bracket.round.php' + 'echo_statement > parenthesized_expression > "("': 'punctuation.definition.arguments.begin.bracket.round.php' + 'echo_statement > parenthesized_expression > ")"': 'punctuation.definition.arguments.end.bracket.round.php' + 'array_creation_expression > "("': 'punctuation.definition.array.begin.bracket.round.php' + 'array_creation_expression > ")"': 'punctuation.definition.array.end.bracket.round.php' + 'formal_parameters > "("': 'punctuation.definition.parameters.begin.bracket.round.php' + 'formal_parameters > ")"': 'punctuation.definition.parameters.end.bracket.round.php' + 'anonymous_function_use_clause > "("': 'punctuation.definition.parameters.begin.bracket.round.php' + 'anonymous_function_use_clause > ")"': 'punctuation.definition.parameters.end.bracket.round.php' + 'catch_clause > "("': 'punctuation.definition.parameters.begin.bracket.round.php' + 'catch_clause > ")"': 'punctuation.definition.parameters.end.bracket.round.php' '"{"': 'punctuation.definition.begin.bracket.curly.php' '"}"': 'punctuation.definition.end.bracket.curly.php' + 'object_creation_expression > declaration_list > "{"': 'punctuation.definition.class.begin.bracket.curly.php' + 'object_creation_expression > declaration_list > "}"': 'punctuation.definition.class.end.bracket.curly.php' '"["': 'punctuation.section.array.begin.php' '"]"': 'punctuation.section.array.end.php' '";"': 'punctuation.terminator.expression.php' - '"static"': 'storage.modifier' - '"public"': 'storage.modifier' - '"private"': 'storage.modifier' - '"protected"': 'storage.modifier' + 'visibility_modifier, static_modifier': 'storage.modifier.php' '"global"': 'storage.modifier' '"const"': 'storage.modifier' - '"abstract"': 'storage.modifier.abstract' - '"extends"': 'storage.modifier.extends' - '"implements"': 'storage.modifier.implements' - '"final"': 'storage.modifier.final' - '"use"': 'keyword.other.use' - '"namespace"': 'keyword.other.namespace' + '"abstract"': 'storage.modifier.abstract.php' + '"extends"': 'storage.modifier.extends.php' + '"implements"': 'storage.modifier.implements.php' + '"final"': 'storage.modifier.final.php' + '"use"': 'keyword.other.use.php' + '"namespace"': 'keyword.other.namespace.php' '"callable"': 'storage.type' '"var"': 'storage.type' - '"trait"': 'storage.type.trait' - '"class"': 'storage.type.class' - '"interface"': 'storage.type.interface' - '"function"': 'storage.type.function' + '"trait"': 'storage.type.trait.php' + '"class"': 'storage.type.class.php' + '"interface"': 'storage.type.interface.php' + '"function"': 'storage.type.function.php' + '"fn"': 'storage.type.function.php' '"type"': 'storage.type.type' 'cast_expression > cast_type': 'storage.type.cast' - '"+"': 'keyword.operator.php' - '"-"': 'keyword.operator.php' - '"*"': 'keyword.operator.php' - '"/"': 'keyword.operator.php' - '"%"': 'keyword.operator.php' - '"**"': 'keyword.operator.php' - '"="': 'keyword.operator.php' - '"=="': 'keyword.operator.php' - '"==="': 'keyword.operator.php' - '"!="': 'keyword.operator.php' - '"!="': 'keyword.operator.php' - '"!=="': 'keyword.operator.php' - '"<"': 'keyword.operator.php' - '">"': 'keyword.operator.php' - '"<>"': 'keyword.operator.php' - '"<="': 'keyword.operator.php' - '"=>"': 'keyword.operator.php' - '">="': 'keyword.operator.php' - '"<=>"': 'keyword.operator.php' - '"&"': 'keyword.operator.php' - '"|"': 'keyword.operator.php' - '"^"': 'keyword.operator.php' - '"~"': 'keyword.opeator.php' - '"<<"': 'keyword.operator.php' - '">>"': 'keyword.operator.php' - '"++"': 'keyword.operator.php' - '"--"': 'keyword.operator.php' - '"and"': 'keyword.operator.php' - '"or"': 'keyword.operator.php' - '"xor"': 'keyword.operator.php' - '"!"': 'keyword.operator.php' - '"&&"': 'keyword.operator.php' - '"||"': 'keyword.operator.php' - '"."': 'keyword.operator.php' - '"?"': 'keyword.operator.php' - '":"': 'keyword.operator.php' - '"??"': 'keyword.operator.php' - '"as"': 'keyword.logical' - '"new"': 'keyword.new' - '"clone"': 'keyword.clone' - '"insteadof"': 'keyword.insteadof' - '"instanceof"': 'keyword.type' - - '"->"': 'keyword.operator.class' + '"+", "-", "*", "/", "%", "**"': 'keyword.operator.arithmetic.php' + '"=", "+=", "-=", "*=", "/=", "%=", "**=", "&=", "|=", "^=", "<<=", ">>=", "??="': 'keyword.operator.assignment.php' + '"==", "===", "!=", "!==", "<", ">", "<>", "<=", ">=", "<=>"': 'keyword.operator.comparison.php' + '"<<", ">>", "~", "^", "&", "|"': 'keyword.operator.bitwise.php' + '"@"': 'keyword.operator.error-control.php' + '"++", "--"': 'keyword.operator.increment-decrement.php' + '"!", "&&", "||", "and", "or", "xor", "as"': 'keyword.operator.logical.php' + '"?", ":", "?:"': 'keyword.operator.ternary.php' + '"??"': 'keyword.operator.null-coalescing.php' + '"..."': 'keyword.operator.spread.php' + '".", ".="': 'keyword.operator.string.php' + '"=>"': 'keyword.operator.key.php' + '"->", "::"': 'keyword.operator.class.php' + + '"new"': 'keyword.other.new.php' + '"clone"': 'keyword.other.clone.php' + '"instanceof"': 'keyword.operator.type.php' + '","': 'punctuation.separator.delimiter.php' '"if"': 'keyword.control' - '"do"': 'keyword.control' - '"else"': 'keyword.control' '"elseif"': 'keyword.control' + '"else"': 'keyword.control' '"endif"': 'keyword.control' + '"do"': 'keyword.control' '"while"': 'keyword.control' '"endwhile"': 'keyword.control' '"for"': 'keyword.control' @@ -183,18 +344,54 @@ scopes: '"endforeach"': 'keyword.control' '"declare"': 'keyword.control' '"enddeclare"': 'keyword.control' - '"return"': 'keyword.control' - '"break"': 'keyword.control' - '"continue"': 'keyword.control' + '"return"': 'keyword.control.return.php' + '"break"': 'keyword.control.break.php' + '"continue"': 'keyword.control.continue.php' '"throw"': 'keyword.control' - '"try"': 'keyword.control' - '"catch"': 'keyword.control' + '"try"': 'keyword.control.exception.php' + '"catch"': 'keyword.control.exception.catch.php' '"finally"': 'keyword.control' - '"switch"': 'keyword.control' + '"switch"': 'keyword.control.switch.php' '"endswitch"': 'keyword.control' - '"case"': 'keyword.control' - '"default"': 'keyword.control' - '"yield"': 'keyword.control.yield' + '"match"': 'keyword.control.match.php' + '"case"': 'keyword.control.case.php' + '"default"': 'keyword.control.default.php' + '"yield"': 'keyword.control.yield.php' + 'yield_expression > "from"': 'keyword.control.yield-from.php' '"goto"': 'keyword.control.goto' '"exit"': 'keyword.control.exit' '"die"': 'keyword.control.die' + + ''' + include_expression, include_once_expression, + require_expression, require_once_expression + ''': 'meta.include.php' + ''' + include_expression > "include", + require_expression > "require", + include_once_expression > "include_once", + require_once_expression > "require_once", + ''': 'keyword.control.import.include.php' + + 'qualified_name > name': [ + { + # from TM grammar, but removed true/false/null b/c they are named nodes in TS + match: '^(__(FILE|DIR|FUNCTION|CLASS|METHOD|LINE|NAMESPACE)__|ON|OFF|YES|NO|NL|BR|TAB)$', + scopes: 'constant.language.php' + }, + 'support.class.php' + ] + + # FIXME there are *lots* of these... + 'function_call_expression > name': [ + {match: '^(un|is)set$', scopes: 'support.function.php'}, + {exact: '^in_array$', scopes: 'support.function.php'}, + {match: '^array_(filter|map)$', scopes: 'support.function.php'}, + {match: '^str_(replace|contains)$', scopes: 'support.function.php'}, + {match: '^preg_(replace|match)$', scopes: 'support.function.php'}, + {match: '^is_(null|string|int|float|callable)$', scopes: 'support.function.php'}, + {match: '^(empty|list|eval|trim)$', scopes: 'support.function.php'}, + {exact: '^html_entity_decode$', scopes: 'support.function.php'} + + 'entity.name.function.php' + ] From 5a7255dd73c0e55c1e997b6d01c88278a8343e63 Mon Sep 17 00:00:00 2001 From: Clayton Carter Date: Wed, 27 Apr 2022 20:41:58 -0400 Subject: [PATCH 21/21] Add injections and wrapping grammars for tree-sitter grammar --- grammars/tree-sitter-html.cson | 36 +++++++++++ grammars/tree-sitter-phpdoc.cson | 18 ++++++ lib/main.js | 28 +++++++++ package-lock.json | 16 +++++ package.json | 3 + spec/tree-sitter-helpers.js | 101 +++++++++++++++++++++++++++++++ spec/tree-sitter-html-spec.js | 74 ++++++++++++++++++++++ 7 files changed, 276 insertions(+) create mode 100644 grammars/tree-sitter-html.cson create mode 100644 grammars/tree-sitter-phpdoc.cson create mode 100644 lib/main.js create mode 100644 spec/tree-sitter-helpers.js create mode 100644 spec/tree-sitter-html-spec.js diff --git a/grammars/tree-sitter-html.cson b/grammars/tree-sitter-html.cson new file mode 100644 index 00000000..e08248fa --- /dev/null +++ b/grammars/tree-sitter-html.cson @@ -0,0 +1,36 @@ +# This grammar is responsible for injecting the actual PHP or HTML grammars +# For the actual PHP scopes, see tree-sitter-php.cson +name: 'PHP' +scopeName: 'text.html.php' +type: 'tree-sitter' +parser: 'tree-sitter-embedded-php' + +fileTypes: [ + 'aw' + 'ctp' + 'inc' + 'install' + 'module' + 'php' + 'php_cs' + 'php3' + 'php4' + 'php5' + 'phpt' + 'phtml' + 'profile' +] + +firstLineRegex: [ + '^\\s*<\\?([pP][hH][pP]|=|\\s|$)' +] + +scopes: + 'template > content:nth-child(0)': [ + {match: /^\#!.*(?:\s|\/)php\d?(?:$|\s)/, scopes: 'comment.line.shebang.php'} + ], + + 'php': [ + {match: '\\n', scopes: 'meta.embedded.block.php'} + 'meta.embedded.line.php' + ] diff --git a/grammars/tree-sitter-phpdoc.cson b/grammars/tree-sitter-phpdoc.cson new file mode 100644 index 00000000..a79f5893 --- /dev/null +++ b/grammars/tree-sitter-phpdoc.cson @@ -0,0 +1,18 @@ +name: 'PHPDoc' +scopeName: 'comment.block.documentation.phpdoc.php' +type: 'tree-sitter' +parser: 'tree-sitter-phpdoc' + +injectionRegex: 'phpdoc|PHPDoc' + +scopes: + tag_name: 'keyword.other.phpdoc.php' + type_list: 'meta.other.type.phpdoc.php' + 'type_list > "|"': 'punctuation.separator.delimiter.php' + 'array_type > "[]"': 'keyword.other.array.phpdoc.php' + primitive_type: 'keyword.other.type.php' + 'named_type > name': 'support.class.php' + '* > namespace_name_as_prefix': 'support.other.namespace.php' + # FIXME not working + # '* > namespace_name_as_prefix > "\\"': 'punctuation.separator.inheritance.php' + '* > qualified_name > name': 'support.class.php' diff --git a/lib/main.js b/lib/main.js new file mode 100644 index 00000000..cceb6a75 --- /dev/null +++ b/lib/main.js @@ -0,0 +1,28 @@ +exports.activate = function() { + if (!atom.grammars.addInjectionPoint) return + + // inject source.php into text.html.php + atom.grammars.addInjectionPoint('text.html.php', { + type: 'php', + language () { return 'php' }, + content (php) { return php } + }) + + // inject html into text.html.php + atom.grammars.addInjectionPoint('text.html.php', { + type: 'template', + language () { return 'html' }, + content (node) { return node.descendantsOfType('content') } + }) + + // inject phpDoc comments into PHP comments + atom.grammars.addInjectionPoint('source.php', { + type: 'comment', + language (comment) { + if (comment.text.startsWith('/**') && !comment.text.startsWith('/***')) { + return 'phpdoc' + } + }, + content (comment) { return comment } + }) +} diff --git a/package-lock.json b/package-lock.json index 58a9e112..e74478ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -151,6 +151,14 @@ "integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=", "dev": true }, + "tree-sitter-embedded-php": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/tree-sitter-embedded-php/-/tree-sitter-embedded-php-0.0.4.tgz", + "integrity": "sha512-JdINmNoncwZgA7QytmPy0TLrp6tJ7p9nyHZvUUNs7ndV4QtLdIYnRJRIAFTosH4Io1GO6Ok6HN9xsX/5sGCdRA==", + "requires": { + "nan": "^2.14.0" + } + }, "tree-sitter-php-abc": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/tree-sitter-php-abc/-/tree-sitter-php-abc-0.17.0.tgz", @@ -159,6 +167,14 @@ "nan": "^2.14.0" } }, + "tree-sitter-phpdoc": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/tree-sitter-phpdoc/-/tree-sitter-phpdoc-0.0.4.tgz", + "integrity": "sha512-vHJ7dp/l8i6XCiYmQAWRj274ph/wc+JxQV9g3CpJDL3/4hxYN+yjWhRY4rmLQzA2IjWFWKbNwJjJ3+C5A9JodA==", + "requires": { + "nan": "^2.14.0" + } + }, "wordwrap": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", diff --git a/package.json b/package.json index f8e6bc5d..7f1d3e6a 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "atom": "*", "node": "*" }, + "main": "lib/main", "homepage": "http://atom.github.io/language-php", "repository": { "type": "git", @@ -16,7 +17,9 @@ "url": "https://github.com/atom/language-php/issues" }, "dependencies": { + "tree-sitter-embedded-php": "0.0.4", "tree-sitter-php-abc": "^0.17.0", + "tree-sitter-phpdoc": "0.0.4" }, "devDependencies": { "coffeelint": "^1.10.1", diff --git a/spec/tree-sitter-helpers.js b/spec/tree-sitter-helpers.js new file mode 100644 index 00000000..c6d3400a --- /dev/null +++ b/spec/tree-sitter-helpers.js @@ -0,0 +1,101 @@ +const dedent = require("dedent"); + +module.exports = { + // https://github.com/atom/atom/blob/b3d3a52d9e4eb41f33df7b91ad1f8a2657a04487/spec/tree-sitter-language-mode-spec.js#L47-L55 + expectTokensToEqual(editor, expectedTokenLines, startingRow = 1) { + const lastRow = editor.getLastScreenRow(); + + for (let row = startingRow; row <= lastRow - startingRow; row++) { + const tokenLine = editor + .tokensForScreenRow(row) + .map(({ text, scopes }) => ({ + text, + scopes: scopes.map((scope) => + scope + .split(" ") + .map((className) => className.replace("syntax--", "")) + .join(".") + ), + })); + + const expectedTokenLine = expectedTokenLines[row - startingRow]; + + expect(tokenLine.length).toEqual(expectedTokenLine.length); + for (let i = 0; i < tokenLine.length; i++) { + expect(tokenLine[i].text).toEqual( + expectedTokenLine[i].text, + `Token ${i}, row: ${row}` + ); + expect(tokenLine[i].scopes).toEqual( + expectedTokenLine[i].scopes, + `Token ${i}, row: ${row}, token: '${tokenLine[i].text}'` + ); + } + } + }, + + toHaveScopesAtPosition(posn, token, expected, includeEmbeddedScopes = false) { + if (expected === undefined) { + expected = token; + } + if (token === undefined) { + expected = []; + } + + // token is not used at this time; it's just a way to keep note where we are + // in the line + + let filterEmbeddedScopes = (scope) => + includeEmbeddedScopes || + (scope !== "text.html.php" && + scope !== "meta.embedded.block.php" && + scope !== "meta.embedded.line.php"); + + let actual = this.actual + .scopeDescriptorForBufferPosition(posn) + .scopes.filter(filterEmbeddedScopes); + + let notExpected = actual.filter((scope) => !expected.includes(scope)); + let notReceived = expected.filter((scope) => !actual.includes(scope)); + + let pass = notExpected.length === 0 && notReceived.length === 0; + + if (pass) { + this.message = () => "Scopes matched"; + } else { + let line = this.actual.getBuffer().lineForRow(posn[0]); + let caret = " ".repeat(posn[1]) + "^"; + + this.message = () => + `Failure: + Scopes did not match at position [${posn.join(", ")}]: +${line} +${caret} + These scopes were expected but not received: + ${notReceived.join(", ")} + These scopes were received but not expected: + ${notExpected.join(", ")} + `; + } + + return pass; + }, + + setPhpText(content) { + this.setText(` { + const subscription = editor + .getBuffer() + .getLanguageMode() + .onDidChangeHighlighting(() => { + subscription.dispose(); + resolve(); + }); + }); + }, +}; diff --git a/spec/tree-sitter-html-spec.js b/spec/tree-sitter-html-spec.js new file mode 100644 index 00000000..d39b391e --- /dev/null +++ b/spec/tree-sitter-html-spec.js @@ -0,0 +1,74 @@ +const dedent = require("dedent"); +const {expectTokensToEqual, toHaveScopesAtPosition, nextHighlightingUpdate} = require('./tree-sitter-helpers') + +describe("Tree-sitter PHP grammar", () => { + var editor; + + beforeEach(async () => { + atom.config.set("core.useTreeSitterParsers", true); + await atom.packages.activatePackage("language-php"); + await atom.packages.activatePackage("language-html"); + editor = await atom.workspace.open("foo.php"); + }); + + beforeEach(function () { + this.addMatchers({ toHaveScopesAtPosition }); + }); + + describe("loading the grammar", () => { + it('loads the wrapper HTML grammar', () => { + embeddingGrammar = atom.grammars.grammarForScopeName("text.html.php"); + expect(embeddingGrammar).toBeTruthy(); + expect(embeddingGrammar.scopeName).toBe("text.html.php"); + expect(embeddingGrammar.constructor.name).toBe("TreeSitterGrammar"); + // FIXME how to test that all selectors were loaded correctly? Invalid + // selectors may generate errors and it would be great to catch those here. + + // injections + expect(embeddingGrammar.injectionPointsByType.template).toBeTruthy(); + expect(embeddingGrammar.injectionPointsByType.php).toBeTruthy(); + }) + }); + + describe("shebang", () => { + it("recognises shebang on the first line of document", () => { + editor.setText(dedent` + #!/usr/bin/env php + + `); + + // expect(editor).toHaveScopesAtPosition([0, 0], "#!", ["text.html.php", "comment.line.shebang.php", "punctuation.definition.comment.php"], true); + expect(editor).toHaveScopesAtPosition([0, 1], "#!", ["text.html.php", "comment.line.shebang.php", + // FIXME following scopes differ from TM + 'source.html' + ], true); + expect(editor).toHaveScopesAtPosition([0, 2], "/usr/bin/env php", ["text.html.php", "comment.line.shebang.php", + // FIXME following scopes differ from TM + 'source.html' + ], true); + expect(editor).toHaveScopesAtPosition([1, 0], " { + editor.setText(dedent` + + #!/usr/bin/env php + + `); + + expect(editor).toHaveScopesAtPosition([1, 0], "#!", ["text.html.php"], true); + expect(editor).toHaveScopesAtPosition([1, 1], "#!", ["text.html.php", + // FIXME following scopes differ from TM + 'meta.embedded.line.php', + 'source.php', + 'punctuation.section.embedded.begin.php' + ], true); + }); + }); +});