tree-sitter-ucode 0.5.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,17 +2,24 @@
2
2
 
3
3
  Tree-sitter grammar for [ucode](https://github.com/jow-/ucode), the ECMAScript-like scripting language used in OpenWrt.
4
4
 
5
- Two grammars are provided:
5
+ Three grammars are provided:
6
6
 
7
- | Grammar | Scope | File types |
8
- |---------|-------|------------|
9
- | `ucode` | `source.uc` | `.uc`, `.ucode`, `.ut` |
10
- | `ucode_markup` | `source.ucode.markup` | `.uc`, `.ucode`, `.ut`, `.uc.tmpl` (template files — detected by content) |
7
+ | Grammar | Scope | File types | Purpose |
8
+ |---------|-------|------------|---------|
9
+ | `ucode` | `source.uc` | `.uc`, `.ucode`, `.ut` | Plain ucode source files |
10
+ | `ucode_markup` | `source.ucode.markup` | `.uc`, `.ucode`, `.ut` (template files — detected by content) | Ucode template files mixing raw text and code tags |
11
+ | `ucdocs` | — | injected | JSDoc-style `/** */` doc comment blocks |
12
+
13
+ `ucode` and `ucode_markup` share file extensions. Template files are distinguished from plain
14
+ code files by content: any file containing a tag opener (`{%`, `{{`, or `{#`) at the start
15
+ of a line (with optional leading whitespace) is automatically parsed by `ucode_markup`. Plain
16
+ code files fall back to `ucode`. See [File-type detection](#file-type-detection) below.
11
17
 
12
- Both grammars share the same file extensions. Template files are distinguished from plain
13
- code files by content: any file whose first tag opener (`{%`, `{{`, or `{#`) appears at
14
- the start of a line is automatically parsed by `ucode_markup`. Plain code files fall back
15
- to `ucode`. See [File-type detection](#file-type-detection) below.
18
+ `ucdocs` is not a standalone file grammar it is automatically injected by the `ucode` and
19
+ `ucode_markup` grammars into every `/** */` doc comment block. Tools that load grammars
20
+ directly from `tree-sitter.json` (including the tree-sitter CLI) handle this automatically.
21
+ Editor plugins may require registering the `ucdocs` grammar separately see the editor
22
+ sections below.
16
23
 
17
24
  ## Ucode vs JavaScript
18
25
 
@@ -29,6 +36,34 @@ Ucode is an ECMAScript subset with OpenWrt-specific extensions. Key differences:
29
36
  | Regex flags | `g`, `i`, `s` only | Full set |
30
37
  | Module system | Static `import`/`export` only; no `from` on re-exports | Full ES modules |
31
38
 
39
+ ## Doc comment grammar (ucdocs)
40
+
41
+ `/** */` blocks are parsed by the `ucdocs` grammar and injected into the host parse tree.
42
+ The grammar understands the following tags:
43
+
44
+ | Tag | Syntax |
45
+ |-----|--------|
46
+ | `@param` | `@param {Type} name description` |
47
+ | `@returns` / `@return` | `@returns {Type} description` |
48
+ | `@throws` / `@throw` | `@throws {Type} description` |
49
+ | `@type` | `@type {Type}` |
50
+ | `@typedef` | `@typedef {Type} TypeName` |
51
+ | `@template` | `@template T, U` |
52
+ | `@function` | `@function module:path#member` |
53
+ | `@module` | `@module name` |
54
+ | `@deprecated` | `@deprecated description` |
55
+ | `@since` | `@since version` |
56
+ | `@see` | `@see reference` |
57
+ | `@example` | `@example code` |
58
+ | `@default` | `@default value` |
59
+
60
+ Type expressions support: primitives (`int`, `float`, `string`, `boolean`, `null`, `void`,
61
+ `function`), `*`/`any`, `list<T>`, `dict<T>`, record types (`{field: T}`), named types
62
+ (`TypeName`, `TypeName<T, U>`), cross-module refs (`module:path.To.Type`), named function
63
+ types `(name: T) => U`, anonymous function types `function(T): U`, union `T | U`, nullable
64
+ `?T`, and array postfix `T[]`. Inline `{@link ...}` tags and optional params `[name=default]`
65
+ are also supported.
66
+
32
67
  ## Requirements
33
68
 
34
69
  - [tree-sitter CLI](https://github.com/tree-sitter/tree-sitter) ≥ 0.24
@@ -38,10 +73,10 @@ Ucode is an ECMAScript subset with OpenWrt-specific extensions. Key differences:
38
73
 
39
74
  ```sh
40
75
  npm install
41
- npm run build # generate + compile Node.js bindings
76
+ npm run build # generate + compile Node.js bindings (ucode and ucode_markup only)
42
77
  ```
43
78
 
44
- To regenerate parsers after editing a grammar file:
79
+ To regenerate parsers after editing a grammar file (run from the repo root):
45
80
 
46
81
  ```sh
47
82
  # ucode grammar
@@ -49,27 +84,33 @@ npx tree-sitter generate
49
84
 
50
85
  # ucode_markup grammar (generated from grammar.js — do not edit markup/grammar.js directly)
51
86
  node scripts/generate-markup-grammar.js
52
- cd markup && npx tree-sitter generate
87
+ npx tree-sitter generate markup/grammar.js --output markup/src
88
+
89
+ # ucdocs grammar (not included in npm run build — must be regenerated manually)
90
+ npx tree-sitter generate ucdocs/grammar.js --output ucdocs/src
53
91
  ```
54
92
 
55
93
  ## Test
56
94
 
57
95
  ```sh
58
- npm test # runs tree-sitter test for ucode and ucode_markup
96
+ npm test # builds and tests all three grammars (ucode, ucode_markup, ucdocs)
59
97
  ```
60
98
 
61
- To filter by corpus file name:
99
+ To filter by corpus file name (run from the repo root):
62
100
 
63
101
  ```sh
64
102
  npx tree-sitter test --file-name control_flow
65
- cd markup && npx tree-sitter test --file-name markup
103
+ (cd markup && npx tree-sitter test --file-name markup)
104
+ (cd ucdocs && npx tree-sitter test --file-name tags)
105
+ (cd ucdocs && npx tree-sitter test --file-name types)
66
106
  ```
67
107
 
68
108
  ## File-type detection
69
109
 
70
- Both grammars claim the same file extensions. Tools that respect `content-regex` in
110
+ Both `ucode` and `ucode_markup` claim the same file extensions. Tools that respect `content-regex` in
71
111
  `tree-sitter.json` (including the tree-sitter CLI ≥ 0.24) automatically route
72
- template files to `ucode_markup` when a tag opener appears at the start of a line.
112
+ template files to `ucode_markup` when a tag opener (`{%`, `{{`, or `{#`) appears at
113
+ the start of a line (with optional leading whitespace).
73
114
  Editors that manage their own filetype dispatch (Neovim, Helix) need an explicit
74
115
  rule — see the editor sections below.
75
116
 
@@ -102,11 +143,15 @@ grammar = "ucode_markup"
102
143
 
103
144
  [[grammar]]
104
145
  name = "ucode"
105
- source = { git = "https://github.com/m00qek/tree-sitter-ucode", rev = "v0.5.0" }
146
+ source = { git = "https://github.com/m00qek/tree-sitter-ucode", rev = "v0.6.0" }
106
147
 
107
148
  [[grammar]]
108
149
  name = "ucode_markup"
109
- source = { git = "https://github.com/m00qek/tree-sitter-ucode", rev = "v0.5.0", subpath = "markup" }
150
+ source = { git = "https://github.com/m00qek/tree-sitter-ucode", rev = "v0.6.0", subpath = "markup" }
151
+
152
+ [[grammar]]
153
+ name = "ucdocs"
154
+ source = { git = "https://github.com/m00qek/tree-sitter-ucode", rev = "v0.6.0", subpath = "ucdocs" }
110
155
  ```
111
156
 
112
157
  Helix does not support content-based filetype detection for shared extensions. For
@@ -38,3 +38,9 @@
38
38
  (for_alt_statement open: (_) increment: (_) @injection.content (#set! injection.language "ucode"))
39
39
 
40
40
  (for_in_alt_statement open: (_) right: (_) @injection.content (#set! injection.language "ucode"))
41
+
42
+ ; Inject ucdocs into JSDoc block comments (/** ... */).
43
+ ; The [^*/] guard excludes /*** section dividers (≥3 stars) and /**/ (empty, non-JSDoc).
44
+ ((comment) @injection.content
45
+ (#match? @injection.content "^/\\*\\*[^*/]")
46
+ (#set! injection.language "ucdocs"))
@@ -134501,8 +134501,8 @@ TS_PUBLIC const TSLanguage *tree_sitter_ucode_markup(void) {
134501
134501
  .max_reserved_word_set_size = 28,
134502
134502
  .metadata = {
134503
134503
  .major_version = 0,
134504
- .minor_version = 5,
134505
- .patch_version = 0,
134504
+ .minor_version = 6,
134505
+ .patch_version = 1,
134506
134506
  },
134507
134507
  };
134508
134508
  return &language;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tree-sitter-ucode",
3
- "version": "0.5.0",
3
+ "version": "0.6.1",
4
4
  "description": "Ucode grammar for tree-sitter",
5
5
  "repository": {
6
6
  "type": "git",
@@ -0,0 +1,5 @@
1
+ ; Inject ucdocs into JSDoc block comments (/** ... */).
2
+ ; The [^*/] guard excludes /*** section dividers (≥3 stars) and /**/ (empty, non-JSDoc).
3
+ ((comment) @injection.content
4
+ (#match? @injection.content "^/\\*\\*[^*/]")
5
+ (#set! injection.language "ucdocs"))
package/src/parser.c CHANGED
@@ -113826,8 +113826,8 @@ TS_PUBLIC const TSLanguage *tree_sitter_ucode(void) {
113826
113826
  .max_reserved_word_set_size = 28,
113827
113827
  .metadata = {
113828
113828
  .major_version = 0,
113829
- .minor_version = 5,
113830
- .patch_version = 0,
113829
+ .minor_version = 6,
113830
+ .patch_version = 1,
113831
113831
  },
113832
113832
  };
113833
113833
  return &language;
@@ -400,9 +400,7 @@ static bool scan_automatic_semicolon(TSLexer *lexer) {
400
400
  }
401
401
 
402
402
  static bool scan_ternary_qmark(TSLexer *lexer) {
403
- while (lexer->lookahead != '\r' && lexer->lookahead != '\n' &&
404
- lexer->lookahead != 0x2028 && lexer->lookahead != 0x2029 &&
405
- iswspace(lexer->lookahead))
403
+ while (iswspace(lexer->lookahead))
406
404
  skip(lexer);
407
405
 
408
406
  if (lexer->lookahead != '?') return false;
Binary file
Binary file
package/tree-sitter.json CHANGED
@@ -14,9 +14,21 @@
14
14
  "locals": [
15
15
  "queries/locals.scm"
16
16
  ],
17
+ "injections": [
18
+ "queries/injections.scm"
19
+ ],
17
20
  "tags": [
18
21
  "queries/tags.scm"
19
22
  ],
23
+ "folds": [
24
+ "queries/folds.scm"
25
+ ],
26
+ "indents": [
27
+ "queries/indents.scm"
28
+ ],
29
+ "textobjects": [
30
+ "queries/textobjects.scm"
31
+ ],
20
32
  "injection-regex": "^ucode$"
21
33
  },
22
34
  {
@@ -66,7 +78,7 @@
66
78
  }
67
79
  ],
68
80
  "metadata": {
69
- "version": "0.5.0",
81
+ "version": "0.6.1",
70
82
  "license": "MIT",
71
83
  "description": "Ucode grammar for tree-sitter",
72
84
  "links": {
package/ucdocs/grammar.js CHANGED
@@ -33,28 +33,24 @@ module.exports = grammar({
33
33
  $.see_tag,
34
34
  $.example_tag,
35
35
  $.default_tag,
36
+ $.function_tag,
37
+ $.module_tag,
36
38
  $.unknown_tag,
37
39
  )),
38
40
  $._end,
39
41
  ),
40
42
 
41
- _begin: _ => seq('/', repeat('*')),
42
- _end: _ => '/',
43
+ _begin: _ => token(seq('/', /\*+/)),
44
+ _end: _ => token(seq(/\*+/, '/')),
43
45
 
44
46
  // Used after a type_expression/rest_type_expression has already claimed `{` at
45
47
  // this position (param_tag, returns_tag, throws_tag) — excludes _brace_text so
46
48
  // it never competes with a legitimate {type} for the leading `{`.
47
- _typed_description: $ => prec.right(seq(
48
- choice($._text, $.inline_tag),
49
- repeat(choice($._text, $.inline_tag)),
50
- )),
49
+ _typed_description: $ => repeat1(choice($._text, $.inline_tag)),
51
50
 
52
51
  // Used wherever no type_expression can appear at the same position, so a bare
53
52
  // `{` can only be an inline tag or arbitrary brace-text (e.g. @example code).
54
- _free_description: $ => prec.right(seq(
55
- choice($._text, $.inline_tag, $._brace_text),
56
- repeat(choice($._text, $.inline_tag, $._brace_text)),
57
- )),
53
+ _free_description: $ => repeat1(choice($._text, $.inline_tag, $._brace_text)),
58
54
 
59
55
  // {@link target text} and similar inline JSDoc tags embedded in description text.
60
56
  inline_tag: $ => seq(
@@ -159,6 +155,29 @@ module.exports = grammar({
159
155
  optional(field('description', alias($._free_description, $.description))),
160
156
  ),
161
157
 
158
+ // @function module:X.Y#Z — identifies the qualified name of the documented function.
159
+ function_tag: $ => seq(
160
+ '@function',
161
+ optional(field('namepath', $.namepath)),
162
+ ),
163
+
164
+ // @module name — identifies the module this file documents.
165
+ module_tag: $ => seq(
166
+ '@module',
167
+ field('name', $.member_name),
168
+ ),
169
+
170
+ // module:X.Y#Z — a namepath referencing a specific member within a module.
171
+ // The #member suffix distinguishes instance methods from the module path itself.
172
+ namepath: $ => seq(
173
+ 'module:',
174
+ field('path', $.module_path),
175
+ optional(seq('#', field('member', $.member_name))),
176
+ ),
177
+
178
+ // Member names cover lowercase (error), uppercase (ERR), and underscored (ulog_open).
179
+ member_name: _ => /[a-zA-Z_$][a-zA-Z0-9_$]*/,
180
+
162
181
  unknown_tag: $ => seq(
163
182
  $.tag_name,
164
183
  optional(field('description', alias($._free_description, $.description))),
@@ -185,9 +204,11 @@ module.exports = grammar({
185
204
  $.union_type,
186
205
  $.nullable_type,
187
206
  $.any_type,
207
+ $.parenthesized_type,
208
+ $.array_type,
188
209
  ),
189
210
 
190
- primitive_type: _ => choice('int', 'float', 'string', 'boolean', 'null', 'void'),
211
+ primitive_type: _ => choice('int', 'float', 'string', 'boolean', 'null', 'void', 'function'),
191
212
 
192
213
  any_type: _ => choice('*', 'any'),
193
214
 
@@ -259,6 +280,14 @@ module.exports = grammar({
259
280
  optional(seq(':', field('return', $._type))),
260
281
  ),
261
282
 
283
+ // Explicit grouping: (T|U) to override default union associativity or
284
+ // for clarity in complex expressions like ?(T|U).
285
+ parenthesized_type: $ => seq('(', $._type, ')'),
286
+
287
+ // T[] postfix array notation. Precedence 3 > nullable (2) > union (1)
288
+ // so ?T[] == ?(T[]) and T[]|U[] == (T[])|(U[]).
289
+ array_type: $ => prec(3, seq($._type, '[]')),
290
+
262
291
  // Left-associative so T | U | V parses as (T | U) | V.
263
292
  union_type: $ => prec.left(1, seq($._type, '|', $._type)),
264
293
 
@@ -271,7 +300,7 @@ module.exports = grammar({
271
300
  // Lowercase-starting names: parameter names and function param names.
272
301
  identifier: _ => /[a-z_$][a-zA-Z_$0-9]*/,
273
302
 
274
- _text: _ => token(prec(-1, /[^*{}@\s][^*{}@\n]*/)),
303
+ _text: _ => token(prec(-1, /[^*{}@\s][^*{}@\n\r]*/)),
275
304
  },
276
305
  });
277
306
 
@@ -1,4 +1,7 @@
1
+ ; ── Tag keywords ──────────────────────────────────────────────────────────────
2
+
1
3
  (tag_name) @keyword
4
+
2
5
  (param_tag "@param" @keyword)
3
6
  (returns_tag ["@returns" "@return"] @keyword)
4
7
  (template_tag "@template" @keyword)
@@ -10,16 +13,72 @@
10
13
  (see_tag "@see" @keyword)
11
14
  (example_tag "@example" @keyword)
12
15
  (default_tag "@default" @keyword)
16
+ (function_tag "@function" @keyword)
17
+ (module_tag "@module" @keyword)
18
+
19
+ ; ── Names ──────────────────────────────────────────────────────────────────
13
20
 
14
21
  (type_param) @type.parameter
15
22
  (type_identifier) @type
16
23
  (primitive_type) @type.builtin
17
24
  (any_type) @type.builtin
18
- (module_type "module:" @module)
19
- (module_path) @module
20
25
 
21
26
  (identifier) @variable.parameter
27
+ (record_field name: (identifier) @variable.member)
28
+ (member_name) @variable.member
29
+
22
30
  (default_value) @constant
23
- (description) @comment
31
+
32
+ ; ── Module paths ────────────────────────────────────────────────────────────
33
+
34
+ (module_type "module:" @module)
35
+ (module_type path: (module_path) @module)
36
+ (namepath "module:" @module)
37
+ (namepath path: (module_path) @module)
38
+
39
+ ; ── Operators ───────────────────────────────────────────────────────────────
40
+
41
+ (union_type "|" @operator)
42
+ (nullable_type "?" @operator)
43
+ (function_type "=>" @operator)
44
+ (optional_param "=" @operator)
45
+
46
+ ; ── Punctuation: brackets ────────────────────────────────────────────────────
47
+
48
+ (type_expression "{" @punctuation.bracket "}" @punctuation.bracket)
49
+ (rest_type_expression "{" @punctuation.bracket "}" @punctuation.bracket)
50
+ (record_type "{" @punctuation.bracket "}" @punctuation.bracket)
51
+ (parenthesized_type "(" @punctuation.bracket ")" @punctuation.bracket)
52
+ (function_type "(" @punctuation.bracket ")" @punctuation.bracket)
53
+ (anon_function_type "(" @punctuation.bracket ")" @punctuation.bracket)
54
+ (named_type "<" @punctuation.bracket ">" @punctuation.bracket)
55
+ (list_type "<" @punctuation.bracket ">" @punctuation.bracket)
56
+ (dict_type "<" @punctuation.bracket ">" @punctuation.bracket)
57
+ (optional_param "[" @punctuation.bracket "]" @punctuation.bracket)
58
+ (array_type "[]" @punctuation.bracket)
24
59
  (inline_tag "{" @punctuation.bracket "}" @punctuation.bracket)
25
- (inline_tag (tag_name) @keyword)
60
+
61
+ ; ── Punctuation: delimiters ──────────────────────────────────────────────────
62
+
63
+ (record_type "," @punctuation.delimiter)
64
+ (record_field ":" @punctuation.delimiter)
65
+ (function_param ":" @punctuation.delimiter)
66
+ (anon_function_type ":" @punctuation.delimiter)
67
+ (namepath "#" @punctuation.delimiter)
68
+
69
+ ; ── Punctuation: special ─────────────────────────────────────────────────────
70
+
71
+ (rest_type_expression "..." @punctuation.special)
72
+
73
+ ; ── Descriptions ─────────────────────────────────────────────────────────────
74
+
75
+ (description) @comment
76
+
77
+ ; @spell only on prose-bearing tags; @example descriptions contain code.
78
+ (document (description) @spell)
79
+ (param_tag description: (description) @spell)
80
+ (returns_tag description: (description) @spell)
81
+ (throws_tag description: (description) @spell)
82
+ (deprecated_tag description: (description) @spell)
83
+ (since_tag description: (description) @spell)
84
+ (see_tag description: (description) @spell)