From e34111256bbd6033fcb40bd8ee056bb6df78142f Mon Sep 17 00:00:00 2001 From: Ben Woodward <1472981+benwoodward@users.noreply.github.com> Date: Fri, 12 Dec 2025 16:00:32 +1100 Subject: [PATCH] feat: add HelixQL parser Add parser, queries, and tests for HelixQL (helix-db.com), a SQL-like query language for HelixDB. --- SUPPORTED_LANGUAGES.md | 1 + lua/nvim-treesitter/parsers.lua | 8 + runtime/queries/helixql/folds.scm | 12 ++ runtime/queries/helixql/highlights.scm | 259 +++++++++++++++++++++++++ tests/query/highlights/helixql/test.hx | 122 ++++++++++++ 5 files changed, 402 insertions(+) create mode 100644 runtime/queries/helixql/folds.scm create mode 100644 runtime/queries/helixql/highlights.scm create mode 100644 tests/query/highlights/helixql/test.hx diff --git a/SUPPORTED_LANGUAGES.md b/SUPPORTED_LANGUAGES.md index b2c006f84..5be13e46e 100644 --- a/SUPPORTED_LANGUAGES.md +++ b/SUPPORTED_LANGUAGES.md @@ -123,6 +123,7 @@ ecma (queries only)[^ecma] | unstable | `HFIJL` | @steelsojka [haskell_persistent](https://github.com/MercuryTechnologies/tree-sitter-haskell-persistent) | unstable | `HF   ` | @lykahb [hcl](https://github.com/tree-sitter-grammars/tree-sitter-hcl) | unstable | `HFIJ ` | @MichaHoffmann [heex](https://github.com/connorlay/tree-sitter-heex) | unstable | `HFIJL` | @connorlay +[helixql](https://github.com/benwoodward/tree-sitter-helixql) | unsupported | `HF   ` | @benwoodward [helm](https://github.com/ngalaiko/tree-sitter-go-template) | unstable | `HF JL` | @qvalentin [hjson](https://github.com/winston0410/tree-sitter-hjson) | unstable | `HFIJL` | @winston0410 [hlsl](https://github.com/tree-sitter-grammars/tree-sitter-hlsl) | unstable | `HFIJL` | @theHamsta diff --git a/lua/nvim-treesitter/parsers.lua b/lua/nvim-treesitter/parsers.lua index 0f9fc5057..8fc80996b 100644 --- a/lua/nvim-treesitter/parsers.lua +++ b/lua/nvim-treesitter/parsers.lua @@ -912,6 +912,14 @@ return { maintainers = { '@qvalentin' }, tier = 2, }, + helixql = { + install_info = { + branch = 'main', + url = 'https://github.com/benwoodward/tree-sitter-helixql', + }, + maintainers = { '@benwoodward' }, + tier = 4, + }, hjson = { install_info = { revision = '02fa3b79b3ff9a296066da6277adfc3f26cbc9e0', diff --git a/runtime/queries/helixql/folds.scm b/runtime/queries/helixql/folds.scm new file mode 100644 index 000000000..6648551f8 --- /dev/null +++ b/runtime/queries/helixql/folds.scm @@ -0,0 +1,12 @@ +; ============================================================================= +; HelixQL Folds for Neovim Tree-sitter +; ============================================================================= +[ + (node_def) + (edge_def) + (vector_def) + (query_def) + (for_loop) + (object_step) + (create_field) +] @fold diff --git a/runtime/queries/helixql/highlights.scm b/runtime/queries/helixql/highlights.scm new file mode 100644 index 000000000..20fc91ab6 --- /dev/null +++ b/runtime/queries/helixql/highlights.scm @@ -0,0 +1,259 @@ +; ============================================================================= +; HelixQL Syntax Highlighting for Neovim Tree-sitter +; ============================================================================= +; NOTE: In tree-sitter queries, later patterns override earlier ones. +; Put general patterns first, then specific overrides after. +; ----------------------------------------------------------------------------- +; Comments +; ----------------------------------------------------------------------------- +(comment) @comment @spell + +; ----------------------------------------------------------------------------- +; Literals +; ----------------------------------------------------------------------------- +(string_literal) @string + +(integer) @number + +(float) @number.float + +(boolean) @boolean + +(none) @constant.builtin + +(now) @constant.builtin + +; ----------------------------------------------------------------------------- +; Generic identifiers +; ----------------------------------------------------------------------------- +(identifier) @variable + +(identifier_upper) @type + +; ----------------------------------------------------------------------------- +; Keywords +; ----------------------------------------------------------------------------- +[ + "QUERY" + "RETURN" + "FOR" + "IN" +] @keyword + +; Node type keywords (wrapped in their own rules) +(index) @keyword + +(default) @keyword + +(count) @keyword + +(ID) @constant.builtin + +; Keywords inside node types +(where_step + "WHERE" @keyword) + +(range_step + "RANGE" @keyword) + +(update + "UPDATE" @keyword) + +(drop + "DROP" @keyword) + +(exists + "EXISTS" @keyword) + +; Boolean and logical operators +(and + "AND" @keyword.operator) + +(or + "OR" @keyword.operator) + +(GT + "GT" @keyword.operator) + +(GTE + "GTE" @keyword.operator) + +(LT + "LT" @keyword.operator) + +(LTE + "LTE" @keyword.operator) + +(EQ + "EQ" @keyword.operator) + +(NEQ + "NEQ" @keyword.operator) + +; Creation operation keywords +(AddN + "AddN" @function.builtin) + +(AddE + "AddE" @function.builtin) + +(AddV + "AddV" @function.builtin) + +(BatchAddV + "BatchAddV" @function.builtin) + +(search_vector + "SearchV" @function.builtin) + +; ----------------------------------------------------------------------------- +; Schema definition keywords +; ----------------------------------------------------------------------------- +[ + "N::" + "E::" + "V::" +] @keyword.directive + +[ + "From:" + "To:" + "Properties" +] @keyword.modifier + +; ----------------------------------------------------------------------------- +; Graph traversal methods +; ----------------------------------------------------------------------------- +(out_e + "OutE" @function.method) + +(in_e + "InE" @function.method) + +(from_n) @function.method + +(to_n) @function.method + +(out + "Out" @function.method) + +(in_nodes + "In" @function.method) + +(shortest_path + "ShortestPath" @function.method) + +; ----------------------------------------------------------------------------- +; Start nodes - highlight N, E, V as keywords +; ----------------------------------------------------------------------------- +(start_node + "N" @keyword) + +(start_edge + "E" @keyword) + +(start_vector + "V" @keyword) + +; ----------------------------------------------------------------------------- +; Types +; ----------------------------------------------------------------------------- +(named_type) @type.builtin + +(date_type) @type.builtin + +(ID_TYPE) @type.builtin + +; Type arguments in angle brackets +(type_args + (identifier_upper) @type) + +; ----------------------------------------------------------------------------- +; Specific identifier patterns (override generic rules) +; ----------------------------------------------------------------------------- +; Query/function names +(query_def + name: (identifier) @function) + +; Schema definition names +(node_def + name: (identifier_upper) @type.definition) + +(edge_def + name: (identifier_upper) @type.definition) + +(vector_def + name: (identifier_upper) @type.definition) + +; Parameter names in function signatures +(param_def + name: (identifier) @variable.parameter) + +; Field names in schema definitions +(field_def + name: (identifier) @property) + +; Property keys in object creation +(new_field + key: (identifier) @property) + +; Property keys in updates +(update_field + key: (identifier) @property) + +; Property keys in object mapping (left of :) +(mapping_field + (identifier) @property + ":") + +; Object access field +(object_access + field: (identifier) @property) + +; Variable assignment targets (left of <-) +(get_stmt + variable: (identifier) @variable) + +; For loop iterable +(for_loop + iterable: (identifier) @variable) + +; ----------------------------------------------------------------------------- +; Operators +; ----------------------------------------------------------------------------- +[ + "<-" + "=>" + "::" + "." + ".." + "!" +] @operator + +; ----------------------------------------------------------------------------- +; Punctuation +; ----------------------------------------------------------------------------- +[ + ":" + "," +] @punctuation.delimiter + +[ + "(" + ")" + "{" + "}" + "[" + "]" + "<" + ">" +] @punctuation.bracket + +"|" @punctuation.special + +; ----------------------------------------------------------------------------- +; Special patterns +; ----------------------------------------------------------------------------- +; Anonymous traversal underscore +(anonymous_traversal + "_" @variable.builtin) diff --git a/tests/query/highlights/helixql/test.hx b/tests/query/highlights/helixql/test.hx new file mode 100644 index 000000000..d65ba6ceb --- /dev/null +++ b/tests/query/highlights/helixql/test.hx @@ -0,0 +1,122 @@ +// Schema definitions +// <- @comment +N::User { +// <- @keyword.directive +// ^ @type.definition + INDEX id: ID, +//^ @keyword +// ^ @type.builtin + name: String, +//^ @property +// ^ @type.builtin + email: String, + age: I32, +// ^ @type.builtin + created_at: Date DEFAULT NOW +// ^ @type.builtin +// ^ @keyword +// ^ @constant.builtin +} + +E::Follows { +// <- @keyword.directive + From: User, +//^ @keyword.modifier + To: User, +//^ @keyword.modifier + Properties: { +//^ @keyword.modifier + since: Date + } +} + +V::UserEmbedding { +// <- @keyword.directive + user_id: ID, + embedding: [F32] +} + +// Simple query +QUERY getUser(userId: ID) => +// <- @keyword +// ^ @function +// ^ @variable.parameter + user <- N(userId) +//^ @variable +// ^ @operator +// ^ @keyword +// ^ @type + RETURN user +//^ @keyword + +// Query with traversal +QUERY getFollowers(userId: ID) => + user <- N(userId) + followers <- user::InE::FromN +// ^ @function.method +// ^ @function.method + RETURN followers + +// Query with filtering +QUERY getAdults(minAge: I32) => + users <- N::WHERE(_::age::GTE(minAge)) +// ^ @keyword +// ^ @variable.builtin +// ^ @keyword.operator + RETURN users + +// Query with object return +QUERY getUserStats(userId: ID) => + user <- N(userId) + followCount <- user::OutE::COUNT +// ^ @keyword + RETURN { + userId: userId, +// ^ @property + followCount: followCount + } + +// Query with AddN +QUERY createUser(name: String, email: String) => + user <- AddN({ +// ^ @function.builtin + name: name, + email: email + }) + RETURN user + +// Query with AddE +QUERY followUser(fromId: ID, toId: ID) => + from <- N(fromId) + to <- N(toId) + AddE::From(from::ID)::To(to::ID) +//^ @function.builtin + RETURN from + +// Query with FOR loop +QUERY createMultipleUsers(names: [String]) => + FOR name IN names { +//^ @keyword +// ^ @keyword + user <- AddN({name: name}) + } + RETURN NONE +// ^ @constant.builtin + +// Vector search +QUERY findSimilar(embedding: [F32], limit: I32) => + results <- SearchV(embedding, limit) +// ^ @function.builtin + RETURN results + +// Literals +QUERY testLiterals() => + str <- "hello world" +// ^ @string + num <- 42 +// ^ @number + flt <- 3.14 +// ^ @number.float + b <- true +// ^ @boolean + RETURN NONE