feat(codenav): Add symbol support for the Hack language (#64015)

Adds scip-ctags support for the Hack language.  

Noteworthy items
1. I did not add support for modules since they are [not
supported](https://github.com/slackhq/tree-sitter-hack/issues/70) in the
tree-sitter grammar right now.


## Screenshots

![image](https://github.com/user-attachments/assets/5c75a0c3-4b88-4e20-a2be-82e04a89791c)

## Test plan
- [x] Update unit tests
- [x] Manually validate symbol side bar for indexed commits
- [x] Manually validate symbol side bar for unindexed commits
- [x] Validate symbol search for indexed commits
- [x] Validate symbol search for unindexed commits
This commit is contained in:
Matthew Manela 2024-07-30 10:46:00 -04:00 committed by GitHub
parent 70b31c9be7
commit b2cd7e5fee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 302 additions and 0 deletions

View File

@ -0,0 +1,52 @@
; Mark method/function parameters as local
(parameters (parameter)) @local
(namespace_declaration
name: (qualified_identifier (identifier)) @descriptor.namespace @kind.namespace) @scope
(class_declaration
name: (identifier) @descriptor.type @kind.class) @scope
(interface_declaration
name: (identifier) @descriptor.type @kind.interface) @scope
(trait_declaration
name: (identifier) @descriptor.type @kind.trait) @scope
(function_declaration
name: (identifier) @descriptor.method @kind.function
body: (_) @local)
; Match interface declarations that do not have a body
(method_declaration
name: (identifier) @descriptor.method @kind.method
!body)
(method_declaration
name: (identifier) @descriptor.method @kind.constructor (#eq? @descriptor.method "__construct")
body: (_) @local)
(method_declaration
name: (identifier) @descriptor.method @kind.method (#not-eq? @descriptor.method "__construct")
body: (_) @local)
(property_declaration
(property_declarator name: (variable) @descriptor.term @kind.property)
(#transform! "[$](.*)" "$1"))
(const_declaration
(const_declarator name: (identifier) @descriptor.term @kind.constant))
(enum_declaration
name: (identifier) @descriptor.type @kind.enum
) @scope
(enum_class_declaration
name: (identifier) @descriptor.type @kind.enum
) @scope
(enumerator (identifier) @descriptor.term @kind.enummember)
(alias_declaration (identifier) @descriptor.type @kind.typealias)
(type_const_declaration name: (identifier) @descriptor.type @kind.typealias)

View File

@ -213,6 +213,7 @@ mod tags {
create_tags_configuration!(go, ParserId::Go, "go");
create_tags_configuration!(java, ParserId::Java, "java");
create_tags_configuration!(javascript, ParserId::Javascript, "javascript");
create_tags_configuration!(hack, ParserId::Hack, "hack");
create_tags_configuration!(kotlin, ParserId::Kotlin, "kotlin");
create_tags_configuration!(magik, ParserId::Magik, "magik");
create_tags_configuration!(python, ParserId::Python, "python");
@ -230,6 +231,7 @@ mod tags {
ParserId::Go => Some(go()),
ParserId::Java => Some(java()),
ParserId::Javascript => Some(javascript()),
ParserId::Hack => Some(hack()),
ParserId::Kotlin => Some(kotlin()),
ParserId::Magik => Some(magik()),
ParserId::Python => Some(python()),

View File

@ -134,4 +134,6 @@ mod test {
generate_tags_and_snapshot!(Tags, test_tags_perl_example, "example.pl");
generate_tags_and_snapshot!(All, test_tags_magik, test_scip_magik, "globals.magik");
generate_tags_and_snapshot!(All, test_tags_hack, test_scip_hack, "globals.hack");
}

View File

@ -0,0 +1,122 @@
---
source: crates/syntax-analysis/src/lib.rs
expression: dumped
---
namespace SomeNamespace {
// ^^^^^^^^^^^^^ definition(Namespace) scip-ctags SomeNamespace/
const int some_const = 100;
// ^^^^^^^^^^ definition(Constant) scip-ctags SomeNamespace/some_const.
trait SomeTrait {
// ^^^^^^^^^ definition(Trait) scip-ctags SomeNamespace/SomeTrait#
public $logLevel;
// ^^^^^^^^^ definition(Property) scip-ctags SomeNamespace/SomeTrait#logLevel.
protected $thing;
// ^^^^^^ definition(Property) scip-ctags SomeNamespace/SomeTrait#thing.
private $secretFromTrait;
// ^^^^^^^^^^^^^^^^ definition(Property) scip-ctags SomeNamespace/SomeTrait#secretFromTrait.
public function setLogger(SomeInterface $thing) {
// ^^^^^^^^^ definition(Method) scip-ctags SomeNamespace/SomeTrait#setLogger().
$this->thing = $thing;
}
public function log($message, $level) {
// ^^^ definition(Method) scip-ctags SomeNamespace/SomeTrait#log().
$this->thing->log($message, $level);
}
}
interface SomeInterface {
// ^^^^^^^^^^^^^ definition(Interface) scip-ctags SomeNamespace/SomeInterface#
const MAX_NUMBER_ITEMS = 1000;
// ^^^^^^^^^^^^^^^^ definition(Constant) scip-ctags SomeNamespace/SomeInterface#MAX_NUMBER_ITEMS.
protected int $secretFromInterface = 0;
// ^^^^^^^^^^^^^^^^^^^^ definition(Property) scip-ctags SomeNamespace/SomeInterface#secretFromInterface.
public function log($message, $level);
// ^^^ definition(Method) scip-ctags SomeNamespace/SomeInterface#log().
}
class SomeClass implements SomeInterface {
// ^^^^^^^^^ definition(Class) scip-ctags SomeNamespace/SomeClass#
const PI = 3.1415926;
// ^^ definition(Constant) scip-ctags SomeNamespace/SomeClass#PI.
private static int $secretFromClass = 0;
// ^^^^^^^^^^^^^^^^ definition(Property) scip-ctags SomeNamespace/SomeClass#secretFromClass.
public static int $hello = 11;
// ^^^^^^ definition(Property) scip-ctags SomeNamespace/SomeClass#hello.
public int $age = 39;
// ^^^^ definition(Property) scip-ctags SomeNamespace/SomeClass#age.
public function log($message, $level) {
// ^^^ definition(Method) scip-ctags SomeNamespace/SomeClass#log().
echo "Log $message of level $level";
}
}
class Foo implements SomeInterface {
// ^^^ definition(Class) scip-ctags SomeNamespace/Foo#
const type T = string;
// ^ definition(TypeAlias) scip-ctags SomeNamespace/Foo#T#
use SomeTrait;
}
type Foo_alias = Foo;
// ^^^^^^^^^ definition(TypeAlias) scip-ctags SomeNamespace/Foo_alias#
newtype Foo_new = Foo::T;
// ^^^^^^^ definition(TypeAlias) scip-ctags SomeNamespace/Foo_new#
// Top level function
<<__EntryPoint>>
function main() {
// ^^^^ definition(Function) scip-ctags SomeNamespace/main().
$foo = new Foo;
$foo->setLogger(new SomeClass);
$foo->log('It works', 1);
}
}
namespace SomeNamespace\SubNamespace {
// ^^^^^^^^^^^^^^^^^^^^^^^^^^ definition(Namespace) scip-ctags `SomeNamespace\SubNamespace`/
// Generic class and a constructor
class Stack<T> {
// ^^^^^ definition(Class) scip-ctags `SomeNamespace\SubNamespace`/Stack#
private vec<T> $stack;
// ^^^^^^ definition(Property) scip-ctags `SomeNamespace\SubNamespace`/Stack#stack.
private int $stackPtr;
// ^^^^^^^^^ definition(Property) scip-ctags `SomeNamespace\SubNamespace`/Stack#stackPtr.
public function __construct() {
// ^^^^^^^^^^^ definition(Constructor) scip-ctags `SomeNamespace\SubNamespace`/Stack#__construct().
$this->stackPtr = 0;
$this->stack = vec[];
}
public function __dispose(): void {}
// ^^^^^^^^^ definition(Method) scip-ctags `SomeNamespace\SubNamespace`/Stack#__dispose().
}
enum Colors: int {
// ^^^^^^ definition(Enum) scip-ctags `SomeNamespace\SubNamespace`/Colors#
Red = 1;
// ^^^ definition(EnumMember) scip-ctags `SomeNamespace\SubNamespace`/Colors#Red.
Green = 2;
// ^^^^^ definition(EnumMember) scip-ctags `SomeNamespace\SubNamespace`/Colors#Green.
Blue = 3;
// ^^^^ definition(EnumMember) scip-ctags `SomeNamespace\SubNamespace`/Colors#Blue.
Default = 4;
// ^^^^^^^ definition(EnumMember) scip-ctags `SomeNamespace\SubNamespace`/Colors#Default.
}
enum class Random: mixed {
// ^^^^^^ definition(Enum) scip-ctags `SomeNamespace\SubNamespace`/Random#
int X = 42;
// ^ definition(EnumMember) scip-ctags `SomeNamespace\SubNamespace`/Random#X.
string S = 'foo';
// ^ definition(EnumMember) scip-ctags `SomeNamespace\SubNamespace`/Random#S.
}
}
// Validate anonymous namespace
namespace {
const int another_const = 88;
// ^^^^^^^^^^^^^ definition(Constant) scip-ctags another_const.
}

View File

@ -0,0 +1,42 @@
---
source: crates/syntax-analysis/src/lib.rs
expression: "String::from_utf8_lossy(&output)"
---
{"_type":"tag","name":"SomeNamespace","path":"globals.hack","language":"hack","line":1,"kind":"namespace","scope":null}
{"_type":"tag","name":"SomeTrait","path":"globals.hack","language":"hack","line":3,"kind":"trait","scope":"SomeNamespace"}
{"_type":"tag","name":"log","path":"globals.hack","language":"hack","line":12,"kind":"method","scope":"SomeNamespace.SomeTrait"}
{"_type":"tag","name":"setLogger","path":"globals.hack","language":"hack","line":8,"kind":"method","scope":"SomeNamespace.SomeTrait"}
{"_type":"tag","name":"secretFromTrait","path":"globals.hack","language":"hack","line":6,"kind":"property","scope":"SomeNamespace.SomeTrait"}
{"_type":"tag","name":"thing","path":"globals.hack","language":"hack","line":5,"kind":"property","scope":"SomeNamespace.SomeTrait"}
{"_type":"tag","name":"logLevel","path":"globals.hack","language":"hack","line":4,"kind":"property","scope":"SomeNamespace.SomeTrait"}
{"_type":"tag","name":"SomeInterface","path":"globals.hack","language":"hack","line":17,"kind":"interface","scope":"SomeNamespace"}
{"_type":"tag","name":"log","path":"globals.hack","language":"hack","line":20,"kind":"method","scope":"SomeNamespace.SomeInterface"}
{"_type":"tag","name":"secretFromInterface","path":"globals.hack","language":"hack","line":19,"kind":"property","scope":"SomeNamespace.SomeInterface"}
{"_type":"tag","name":"MAX_NUMBER_ITEMS","path":"globals.hack","language":"hack","line":18,"kind":"constant","scope":"SomeNamespace.SomeInterface"}
{"_type":"tag","name":"SomeClass","path":"globals.hack","language":"hack","line":23,"kind":"class","scope":"SomeNamespace"}
{"_type":"tag","name":"log","path":"globals.hack","language":"hack","line":28,"kind":"method","scope":"SomeNamespace.SomeClass"}
{"_type":"tag","name":"age","path":"globals.hack","language":"hack","line":27,"kind":"property","scope":"SomeNamespace.SomeClass"}
{"_type":"tag","name":"hello","path":"globals.hack","language":"hack","line":26,"kind":"property","scope":"SomeNamespace.SomeClass"}
{"_type":"tag","name":"secretFromClass","path":"globals.hack","language":"hack","line":25,"kind":"property","scope":"SomeNamespace.SomeClass"}
{"_type":"tag","name":"PI","path":"globals.hack","language":"hack","line":24,"kind":"constant","scope":"SomeNamespace.SomeClass"}
{"_type":"tag","name":"Foo","path":"globals.hack","language":"hack","line":33,"kind":"class","scope":"SomeNamespace"}
{"_type":"tag","name":"T","path":"globals.hack","language":"hack","line":34,"kind":"typeAlias","scope":"SomeNamespace.Foo"}
{"_type":"tag","name":"main","path":"globals.hack","language":"hack","line":43,"kind":"function","scope":"SomeNamespace"}
{"_type":"tag","name":"Foo_new","path":"globals.hack","language":"hack","line":39,"kind":"typeAlias","scope":"SomeNamespace"}
{"_type":"tag","name":"Foo_alias","path":"globals.hack","language":"hack","line":38,"kind":"typeAlias","scope":"SomeNamespace"}
{"_type":"tag","name":"some_const","path":"globals.hack","language":"hack","line":2,"kind":"constant","scope":"SomeNamespace"}
{"_type":"tag","name":"SomeNamespace\\SubNamespace","path":"globals.hack","language":"hack","line":50,"kind":"namespace","scope":null}
{"_type":"tag","name":"Stack","path":"globals.hack","language":"hack","line":52,"kind":"class","scope":"SomeNamespace\\SubNamespace"}
{"_type":"tag","name":"__dispose","path":"globals.hack","language":"hack","line":61,"kind":"method","scope":"SomeNamespace\\SubNamespace.Stack"}
{"_type":"tag","name":"__construct","path":"globals.hack","language":"hack","line":56,"kind":"constructor","scope":"SomeNamespace\\SubNamespace.Stack"}
{"_type":"tag","name":"stackPtr","path":"globals.hack","language":"hack","line":54,"kind":"property","scope":"SomeNamespace\\SubNamespace.Stack"}
{"_type":"tag","name":"stack","path":"globals.hack","language":"hack","line":53,"kind":"property","scope":"SomeNamespace\\SubNamespace.Stack"}
{"_type":"tag","name":"Colors","path":"globals.hack","language":"hack","line":64,"kind":"enum","scope":"SomeNamespace\\SubNamespace"}
{"_type":"tag","name":"Default","path":"globals.hack","language":"hack","line":68,"kind":"enumMember","scope":"SomeNamespace\\SubNamespace.Colors"}
{"_type":"tag","name":"Blue","path":"globals.hack","language":"hack","line":67,"kind":"enumMember","scope":"SomeNamespace\\SubNamespace.Colors"}
{"_type":"tag","name":"Green","path":"globals.hack","language":"hack","line":66,"kind":"enumMember","scope":"SomeNamespace\\SubNamespace.Colors"}
{"_type":"tag","name":"Red","path":"globals.hack","language":"hack","line":65,"kind":"enumMember","scope":"SomeNamespace\\SubNamespace.Colors"}
{"_type":"tag","name":"Random","path":"globals.hack","language":"hack","line":71,"kind":"enum","scope":"SomeNamespace\\SubNamespace"}
{"_type":"tag","name":"S","path":"globals.hack","language":"hack","line":73,"kind":"enumMember","scope":"SomeNamespace\\SubNamespace.Random"}
{"_type":"tag","name":"X","path":"globals.hack","language":"hack","line":72,"kind":"enumMember","scope":"SomeNamespace\\SubNamespace.Random"}
{"_type":"tag","name":"another_const","path":"globals.hack","language":"hack","line":79,"kind":"constant","scope":null}

View File

@ -0,0 +1,80 @@
namespace SomeNamespace {
const int some_const = 100;
trait SomeTrait {
public $logLevel;
protected $thing;
private $secretFromTrait;
public function setLogger(SomeInterface $thing) {
$this->thing = $thing;
}
public function log($message, $level) {
$this->thing->log($message, $level);
}
}
interface SomeInterface {
const MAX_NUMBER_ITEMS = 1000;
protected int $secretFromInterface = 0;
public function log($message, $level);
}
class SomeClass implements SomeInterface {
const PI = 3.1415926;
private static int $secretFromClass = 0;
public static int $hello = 11;
public int $age = 39;
public function log($message, $level) {
echo "Log $message of level $level";
}
}
class Foo implements SomeInterface {
const type T = string;
use SomeTrait;
}
type Foo_alias = Foo;
newtype Foo_new = Foo::T;
// Top level function
<<__EntryPoint>>
function main() {
$foo = new Foo;
$foo->setLogger(new SomeClass);
$foo->log('It works', 1);
}
}
namespace SomeNamespace\SubNamespace {
// Generic class and a constructor
class Stack<T> {
private vec<T> $stack;
private int $stackPtr;
public function __construct() {
$this->stackPtr = 0;
$this->stack = vec[];
}
public function __dispose(): void {}
}
enum Colors: int {
Red = 1;
Green = 2;
Blue = 3;
Default = 4;
}
enum class Random: mixed {
int X = 42;
string S = 'foo';
}
}
// Validate anonymous namespace
namespace {
const int another_const = 88;
}

View File

@ -65,6 +65,7 @@ var supportedLanguages = map[string]struct{}{
"go": {},
"java": {},
"javascript": {},
"hack": {},
"kotlin": {},
"magik": {},
"python": {},
@ -81,6 +82,7 @@ var DefaultEngines = map[string]ParserType{
"c_sharp": ScipCtags,
"go": ScipCtags,
"javascript": ScipCtags,
"hack": ScipCtags,
"kotlin": ScipCtags,
"magik": ScipCtags,
"python": ScipCtags,