rbtr-lang-javascript 2026.9.0.dev0__py3-none-any.whl

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.
Files changed (32) hide show
  1. rbtr_lang_javascript/__init__.py +1 -0
  2. rbtr_lang_javascript/javascript.scm +4 -0
  3. rbtr_lang_javascript/plugin.py +190 -0
  4. rbtr_lang_javascript/py.typed +0 -0
  5. rbtr_lang_javascript/shared.scm +29 -0
  6. rbtr_lang_javascript/tests/__init__.py +0 -0
  7. rbtr_lang_javascript/tests/__snapshots__/test_samples/test_edges_match_snapshot[javascript].json +4 -0
  8. rbtr_lang_javascript/tests/__snapshots__/test_samples/test_edges_match_snapshot[tsx].json +3 -0
  9. rbtr_lang_javascript/tests/__snapshots__/test_samples/test_edges_match_snapshot[typescript].json +6 -0
  10. rbtr_lang_javascript/tests/__snapshots__/test_samples/test_extraction_matches_snapshot[javascript].json +420 -0
  11. rbtr_lang_javascript/tests/__snapshots__/test_samples/test_extraction_matches_snapshot[tsx].json +211 -0
  12. rbtr_lang_javascript/tests/__snapshots__/test_samples/test_extraction_matches_snapshot[typescript].json +610 -0
  13. rbtr_lang_javascript/tests/cases_docstrings.py +225 -0
  14. rbtr_lang_javascript/tests/cases_extraction.py +429 -0
  15. rbtr_lang_javascript/tests/samples/javascript/config.js +2 -0
  16. rbtr_lang_javascript/tests/samples/javascript/javascript.js +46 -0
  17. rbtr_lang_javascript/tests/samples/javascript/styles.css +3 -0
  18. rbtr_lang_javascript/tests/samples/tsx/labels.ts +2 -0
  19. rbtr_lang_javascript/tests/samples/tsx/tsx.tsx +33 -0
  20. rbtr_lang_javascript/tests/samples/typescript/config.ts +2 -0
  21. rbtr_lang_javascript/tests/samples/typescript/types.ts +2 -0
  22. rbtr_lang_javascript/tests/samples/typescript/typescript.ts +68 -0
  23. rbtr_lang_javascript/tests/test_docstrings.py +51 -0
  24. rbtr_lang_javascript/tests/test_extraction.py +80 -0
  25. rbtr_lang_javascript/tests/test_samples.py +95 -0
  26. rbtr_lang_javascript/typescript.scm +36 -0
  27. rbtr_lang_javascript/variables.scm +54 -0
  28. rbtr_lang_javascript-2026.9.0.dev0.dist-info/METADATA +74 -0
  29. rbtr_lang_javascript-2026.9.0.dev0.dist-info/RECORD +32 -0
  30. rbtr_lang_javascript-2026.9.0.dev0.dist-info/WHEEL +4 -0
  31. rbtr_lang_javascript-2026.9.0.dev0.dist-info/entry_points.txt +5 -0
  32. rbtr_lang_javascript-2026.9.0.dev0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1 @@
1
+ """JavaScript and TypeScript language plugin package."""
@@ -0,0 +1,4 @@
1
+ ; JS uses `identifier` for class names (TS uses `type_identifier`).
2
+ (class_declaration
3
+ name: (identifier) @_cls_name) @class
4
+
@@ -0,0 +1,190 @@
1
+ """JavaScript and TypeScript language plugins.
2
+
3
+ Registers three languages (one per tree-sitter grammar) sharing a
4
+ common import extractor and a shared core query (classes, functions,
5
+ generators, variables, imports, and methods — class/object members
6
+ including get/set accessors). TS additionally captures interfaces,
7
+ enums, type aliases, abstract classes, and interface/abstract method
8
+ signatures.
9
+
10
+ Extracted chunks::
11
+
12
+ function greet() {} → function "greet", scope ""
13
+ const add = (a, b) => a + b; → function "add", scope ""
14
+ class Service {} → class "Service", scope ""
15
+
16
+ import { foo } from './models'
17
+ → import, metadata {module: "models", names: "foo", dots: "1"}
18
+ import React from 'react'
19
+ → import, metadata {module: "react", names: "React"}
20
+ import './styles.css'
21
+ → import, metadata {module: "styles", dots: "1"}
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ from pathlib import PurePosixPath
27
+ from typing import TYPE_CHECKING
28
+
29
+ from rbtr.domain.models import ImportMeta
30
+ from rbtr.languages.registration import (
31
+ ImportResolver,
32
+ LanguageRegistration,
33
+ QueryExtraction,
34
+ load_query,
35
+ parse_path_relative,
36
+ )
37
+
38
+ if TYPE_CHECKING:
39
+ from tree_sitter import Node
40
+
41
+ _SHARED_QUERIES = load_query(__package__, "shared") + load_query(__package__, "variables")
42
+ _JS_QUERY = load_query(__package__, "javascript") + _SHARED_QUERIES
43
+ _TS_QUERY = load_query(__package__, "typescript") + _SHARED_QUERIES
44
+
45
+
46
+ # ── Import extractor (shared by JS and TS) ───────────────────────────
47
+
48
+
49
+ def extract_import_meta(
50
+ resolver: ImportResolver, node: Node, captures: dict[str, list[Node]]
51
+ ) -> ImportMeta:
52
+ """Extract import data from a JS/TS `import_statement` node.
53
+
54
+ Reads `@_import_module` from captures, then walks the node
55
+ for import names (which the query can't capture because
56
+ they're multi-valued). Applies `parse_path_relative` and
57
+ extension stripping to the module path.
58
+
59
+ Examples:
60
+
61
+ `import { foo } from './models'`:
62
+ module="models", names="foo", dots="1"
63
+
64
+ `import React from 'react'`:
65
+ module="react", names="React"
66
+
67
+ `import * as utils from '../utils'`:
68
+ module="utils", names="utils", dots="2"
69
+
70
+ `import './styles.css'`:
71
+ module="styles", dots="1"
72
+ """
73
+ meta = resolver(node, captures)
74
+ names: list[str] = []
75
+
76
+ # Module comes from @_import_module capture (string_fragment).
77
+ # Still need to parse relative paths and strip extensions.
78
+ raw_module = meta.module
79
+
80
+ # Import clause (no named fields — child iteration needed).
81
+ for child in node.children:
82
+ if child.type != "import_clause":
83
+ continue
84
+ for ic in child.children:
85
+ match ic.type:
86
+ case "identifier":
87
+ if ic.text:
88
+ names.append(ic.text.decode())
89
+ case "named_imports":
90
+ for spec in ic.children:
91
+ if spec.type == "import_specifier":
92
+ sid = spec.child_by_field_name("name")
93
+ if sid and sid.text:
94
+ names.append(sid.text.decode())
95
+ case "namespace_import":
96
+ for ns in ic.children:
97
+ if ns.type == "identifier" and ns.text:
98
+ names.append(ns.text.decode())
99
+ break
100
+
101
+ if raw_module:
102
+ dots, cleaned = parse_path_relative(raw_module)
103
+ if dots:
104
+ meta.dots = str(dots)
105
+ # Strip file extensions so edges.py can match without guessing.
106
+ cleaned = str(PurePosixPath(cleaned).with_suffix("")) or cleaned
107
+ meta.module = cleaned
108
+
109
+ if names:
110
+ meta.names = ",".join(names)
111
+ return meta
112
+
113
+
114
+ # ── Plugin ───────────────────────────────────────────────────────────
115
+
116
+
117
+ javascript = LanguageRegistration(
118
+ id="javascript",
119
+ extensions=frozenset({".js", ".jsx", ".mjs"}),
120
+ grammar_module="tree_sitter_javascript",
121
+ extraction=QueryExtraction(
122
+ query=_JS_QUERY,
123
+ scope_types=frozenset({"class_declaration", "function_declaration"}),
124
+ class_scope_types=frozenset({"class_declaration"}),
125
+ # Both `/** */` JSDoc and `//` comments land in
126
+ # the grammar as a single `comment` node type.
127
+ ),
128
+ index_files=frozenset({"index.js"}),
129
+ import_targets=frozenset({"javascript", "css"}),
130
+ source_roots=("", "src"),
131
+ extraction_serial=6,
132
+ )
133
+ typescript = LanguageRegistration(
134
+ id="typescript",
135
+ extensions=frozenset({".ts"}),
136
+ grammar_module="tree_sitter_typescript",
137
+ grammar_entry="language_typescript",
138
+ extraction=QueryExtraction(
139
+ query=_TS_QUERY,
140
+ scope_types=frozenset(
141
+ {
142
+ "class_declaration",
143
+ "abstract_class_declaration",
144
+ "interface_declaration",
145
+ "function_declaration",
146
+ "internal_module",
147
+ "module",
148
+ "enum_declaration",
149
+ }
150
+ ),
151
+ class_scope_types=frozenset(
152
+ {"class_declaration", "abstract_class_declaration", "interface_declaration"}
153
+ ),
154
+ ),
155
+ index_files=frozenset({"index.ts", "index.js"}),
156
+ import_targets=frozenset({"typescript", "tsx", "javascript", "css"}),
157
+ source_roots=("", "src"),
158
+ extraction_serial=6,
159
+ )
160
+ tsx = LanguageRegistration(
161
+ id="tsx",
162
+ extensions=frozenset({".tsx"}),
163
+ grammar_module="tree_sitter_typescript",
164
+ grammar_entry="language_tsx",
165
+ extraction=QueryExtraction(
166
+ query=_TS_QUERY,
167
+ scope_types=frozenset(
168
+ {
169
+ "class_declaration",
170
+ "abstract_class_declaration",
171
+ "interface_declaration",
172
+ "function_declaration",
173
+ "internal_module",
174
+ "module",
175
+ "enum_declaration",
176
+ }
177
+ ),
178
+ class_scope_types=frozenset(
179
+ {"class_declaration", "abstract_class_declaration", "interface_declaration"}
180
+ ),
181
+ ),
182
+ index_files=frozenset({"index.tsx", "index.ts", "index.js"}),
183
+ import_targets=frozenset({"tsx", "typescript", "javascript", "css"}),
184
+ source_roots=("", "src"),
185
+ extraction_serial=3,
186
+ )
187
+
188
+ javascript.import_extractor(extract_import_meta)
189
+ typescript.import_extractor(extract_import_meta)
190
+ tsx.import_extractor(extract_import_meta)
File without changes
@@ -0,0 +1,29 @@
1
+ ; Shared patterns — identical across the JS and TS grammars.
2
+ ; Comments (JS/TS: single `comment` type for `//` and `/* */`, incl. JSDoc).
3
+ (comment) @comment
4
+
5
+ ; method_definition covers class and object-literal members, generators,
6
+ ; and get/set accessors; inside a class it promotes to a method scoped to
7
+ ; the class, elsewhere it is a scope-less method.
8
+ (function_declaration
9
+ name: (identifier) @_fn_name) @function
10
+
11
+ (generator_function_declaration
12
+ name: (identifier) @_fn_name) @function
13
+
14
+ (import_statement
15
+ source: (string (string_fragment) @_import_module)) @import
16
+
17
+ (lexical_declaration
18
+ (variable_declarator
19
+ name: (identifier) @_fn_name
20
+ value: (arrow_function))) @function
21
+
22
+ (method_definition
23
+ name: (property_identifier) @_method_name) @method
24
+
25
+ ; A re-export reads from another module, so it resolves as an import, and
26
+ ; it is how a symbol reaches a consumer that never names its defining
27
+ ; file.
28
+ (export_statement
29
+ source: (string (string_fragment) @_import_module)) @import
File without changes
@@ -0,0 +1,4 @@
1
+ [
2
+ "javascript.js::import \"./styles.css\"; -> styles.css::.greeter [imports]",
3
+ "javascript.js::import { LOCALE } from \"./config.js\"; -> config.js::LOCALE [imports]"
4
+ ]
@@ -0,0 +1,3 @@
1
+ [
2
+ "tsx.tsx::import { INITIAL_LABEL } from \"./labels\"; -> labels.ts::INITIAL_LABEL [imports]"
3
+ ]
@@ -0,0 +1,6 @@
1
+ [
2
+ "typescript.ts::export * from \"./config\"; -> config.ts::LOCALE [imports]",
3
+ "typescript.ts::export { Locale } from \"./types\"; -> types.ts::Formatter [imports]",
4
+ "typescript.ts::import type { Formatter } from \"./types\"; -> types.ts::Formatter [imports]",
5
+ "typescript.ts::import { LOCALE } from \"./config\"; -> config.ts::LOCALE [imports]"
6
+ ]
@@ -0,0 +1,420 @@
1
+ [
2
+ {
3
+ "blob_sha": "sha1",
4
+ "file_path": "config.js",
5
+ "kind": "comment",
6
+ "name": "",
7
+ "scope": "",
8
+ "language": "javascript",
9
+ "file_language": "javascript",
10
+ "content": "// Locale configuration for the greeter.",
11
+ "line_start": 1,
12
+ "line_end": 1,
13
+ "metadata": {
14
+ "module": "",
15
+ "names": "",
16
+ "dots": "",
17
+ "language_hint": ""
18
+ },
19
+ "id": "1086b120f17a34b9"
20
+ },
21
+ {
22
+ "blob_sha": "sha1",
23
+ "file_path": "config.js",
24
+ "kind": "variable",
25
+ "name": "LOCALE",
26
+ "scope": "",
27
+ "language": "javascript",
28
+ "file_language": "javascript",
29
+ "content": "LOCALE = \"en\"",
30
+ "line_start": 2,
31
+ "line_end": 2,
32
+ "metadata": {
33
+ "module": "",
34
+ "names": "",
35
+ "dots": "",
36
+ "language_hint": ""
37
+ },
38
+ "id": "79b96a86c2a4e227"
39
+ },
40
+ {
41
+ "blob_sha": "sha1",
42
+ "file_path": "javascript.js",
43
+ "kind": "comment",
44
+ "name": "",
45
+ "scope": "",
46
+ "language": "javascript",
47
+ "file_language": "javascript",
48
+ "content": "// Greeter — format greetings for named recipients.\n//\n// The JavaScript plugin extracts function declarations, generator\n// functions, arrow functions bound to consts, classes, module-level\n// const/let variables (including destructuring), imports, and methods\n// (class members, including get/set accessors; object-literal methods are\n// captured without a scope).",
49
+ "line_start": 1,
50
+ "line_end": 7,
51
+ "metadata": {
52
+ "module": "",
53
+ "names": "",
54
+ "dots": "",
55
+ "language_hint": ""
56
+ },
57
+ "id": "cc08702b255e2178"
58
+ },
59
+ {
60
+ "blob_sha": "sha1",
61
+ "file_path": "javascript.js",
62
+ "kind": "import",
63
+ "name": "import { LOCALE } from \"./config.js\";",
64
+ "scope": "",
65
+ "language": "javascript",
66
+ "file_language": "javascript",
67
+ "content": "import { LOCALE } from \"./config.js\";",
68
+ "line_start": 9,
69
+ "line_end": 9,
70
+ "metadata": {
71
+ "module": "config",
72
+ "names": "LOCALE",
73
+ "dots": "1",
74
+ "language_hint": ""
75
+ },
76
+ "id": "19dbd9da06350928"
77
+ },
78
+ {
79
+ "blob_sha": "sha1",
80
+ "file_path": "javascript.js",
81
+ "kind": "import",
82
+ "name": "import format from \"formatter\";",
83
+ "scope": "",
84
+ "language": "javascript",
85
+ "file_language": "javascript",
86
+ "content": "import format from \"formatter\";",
87
+ "line_start": 10,
88
+ "line_end": 10,
89
+ "metadata": {
90
+ "module": "formatter",
91
+ "names": "format",
92
+ "dots": "",
93
+ "language_hint": ""
94
+ },
95
+ "id": "b9fdecded9f3899d"
96
+ },
97
+ {
98
+ "blob_sha": "sha1",
99
+ "file_path": "javascript.js",
100
+ "kind": "import",
101
+ "name": "import \"./styles.css\";",
102
+ "scope": "",
103
+ "language": "javascript",
104
+ "file_language": "javascript",
105
+ "content": "import \"./styles.css\";",
106
+ "line_start": 11,
107
+ "line_end": 11,
108
+ "metadata": {
109
+ "module": "styles",
110
+ "names": "",
111
+ "dots": "1",
112
+ "language_hint": ""
113
+ },
114
+ "id": "6f2daecdfe38687e"
115
+ },
116
+ {
117
+ "blob_sha": "sha1",
118
+ "file_path": "javascript.js",
119
+ "kind": "variable",
120
+ "name": "DEFAULT_GREETING",
121
+ "scope": "",
122
+ "language": "javascript",
123
+ "file_language": "javascript",
124
+ "content": "DEFAULT_GREETING = \"Hello\"",
125
+ "line_start": 13,
126
+ "line_end": 13,
127
+ "metadata": {
128
+ "module": "",
129
+ "names": "",
130
+ "dots": "",
131
+ "language_hint": ""
132
+ },
133
+ "id": "8486d13cd3f4dad0"
134
+ },
135
+ {
136
+ "blob_sha": "sha1",
137
+ "file_path": "javascript.js",
138
+ "kind": "variable",
139
+ "name": "locale",
140
+ "scope": "",
141
+ "language": "javascript",
142
+ "file_language": "javascript",
143
+ "content": "{ locale, fallback } = LOCALE",
144
+ "line_start": 14,
145
+ "line_end": 14,
146
+ "metadata": {
147
+ "module": "",
148
+ "names": "",
149
+ "dots": "",
150
+ "language_hint": ""
151
+ },
152
+ "id": "9aecdd2952d7477d"
153
+ },
154
+ {
155
+ "blob_sha": "sha1",
156
+ "file_path": "javascript.js",
157
+ "kind": "variable",
158
+ "name": "fallback",
159
+ "scope": "",
160
+ "language": "javascript",
161
+ "file_language": "javascript",
162
+ "content": "{ locale, fallback } = LOCALE",
163
+ "line_start": 14,
164
+ "line_end": 14,
165
+ "metadata": {
166
+ "module": "",
167
+ "names": "",
168
+ "dots": "",
169
+ "language_hint": ""
170
+ },
171
+ "id": "8133eb3ed093cf61"
172
+ },
173
+ {
174
+ "blob_sha": "sha1",
175
+ "file_path": "javascript.js",
176
+ "kind": "comment",
177
+ "name": "",
178
+ "scope": "",
179
+ "language": "javascript",
180
+ "file_language": "javascript",
181
+ "content": "/** Format a greeting for a name. */",
182
+ "line_start": 16,
183
+ "line_end": 16,
184
+ "metadata": {
185
+ "module": "",
186
+ "names": "",
187
+ "dots": "",
188
+ "language_hint": ""
189
+ },
190
+ "id": "f104f05d5b9c37cb"
191
+ },
192
+ {
193
+ "blob_sha": "sha1",
194
+ "file_path": "javascript.js",
195
+ "kind": "function",
196
+ "name": "formatGreeting",
197
+ "scope": "",
198
+ "language": "javascript",
199
+ "file_language": "javascript",
200
+ "content": "function formatGreeting(name) {\n return `${DEFAULT_GREETING}, ${name} (${locale ?? fallback})`;\n}",
201
+ "line_start": 17,
202
+ "line_end": 19,
203
+ "metadata": {
204
+ "module": "",
205
+ "names": "",
206
+ "dots": "",
207
+ "language_hint": ""
208
+ },
209
+ "id": "de271dc4da07feb0"
210
+ },
211
+ {
212
+ "blob_sha": "sha1",
213
+ "file_path": "javascript.js",
214
+ "kind": "function",
215
+ "name": "cachedDefault",
216
+ "scope": "",
217
+ "language": "javascript",
218
+ "file_language": "javascript",
219
+ "content": "const cachedDefault = () => DEFAULT_GREETING;",
220
+ "line_start": 21,
221
+ "line_end": 21,
222
+ "metadata": {
223
+ "module": "",
224
+ "names": "",
225
+ "dots": "",
226
+ "language_hint": ""
227
+ },
228
+ "id": "6a672d8c65feb8f2"
229
+ },
230
+ {
231
+ "blob_sha": "sha1",
232
+ "file_path": "javascript.js",
233
+ "kind": "comment",
234
+ "name": "",
235
+ "scope": "",
236
+ "language": "javascript",
237
+ "file_language": "javascript",
238
+ "content": "/** Stateful greeter holding a prefix. */",
239
+ "line_start": 23,
240
+ "line_end": 23,
241
+ "metadata": {
242
+ "module": "",
243
+ "names": "",
244
+ "dots": "",
245
+ "language_hint": ""
246
+ },
247
+ "id": "afd95abe4fefa643"
248
+ },
249
+ {
250
+ "blob_sha": "sha1",
251
+ "file_path": "javascript.js",
252
+ "kind": "class",
253
+ "name": "Greeter",
254
+ "scope": "",
255
+ "language": "javascript",
256
+ "file_language": "javascript",
257
+ "content": "class Greeter {\n constructor(prefix = DEFAULT_GREETING) {\n this.prefix = prefix;\n }\n\n greet(name) {\n return format(`${this.prefix}, ${name}`);\n }\n}",
258
+ "line_start": 24,
259
+ "line_end": 32,
260
+ "metadata": {
261
+ "module": "",
262
+ "names": "",
263
+ "dots": "",
264
+ "language_hint": ""
265
+ },
266
+ "id": "5dc8fd6afa366584"
267
+ },
268
+ {
269
+ "blob_sha": "sha1",
270
+ "file_path": "javascript.js",
271
+ "kind": "method",
272
+ "name": "constructor",
273
+ "scope": "Greeter",
274
+ "language": "javascript",
275
+ "file_language": "javascript",
276
+ "content": "constructor(prefix = DEFAULT_GREETING) {\n this.prefix = prefix;\n }",
277
+ "line_start": 25,
278
+ "line_end": 27,
279
+ "metadata": {
280
+ "module": "",
281
+ "names": "",
282
+ "dots": "",
283
+ "language_hint": ""
284
+ },
285
+ "id": "f8c2d42e485c98fe"
286
+ },
287
+ {
288
+ "blob_sha": "sha1",
289
+ "file_path": "javascript.js",
290
+ "kind": "method",
291
+ "name": "greet",
292
+ "scope": "Greeter",
293
+ "language": "javascript",
294
+ "file_language": "javascript",
295
+ "content": "greet(name) {\n return format(`${this.prefix}, ${name}`);\n }",
296
+ "line_start": 29,
297
+ "line_end": 31,
298
+ "metadata": {
299
+ "module": "",
300
+ "names": "",
301
+ "dots": "",
302
+ "language_hint": ""
303
+ },
304
+ "id": "54cecd87796a0099"
305
+ },
306
+ {
307
+ "blob_sha": "sha1",
308
+ "file_path": "javascript.js",
309
+ "kind": "comment",
310
+ "name": "",
311
+ "scope": "",
312
+ "language": "javascript",
313
+ "file_language": "javascript",
314
+ "content": "/** Yield each recipient name in turn. */",
315
+ "line_start": 34,
316
+ "line_end": 34,
317
+ "metadata": {
318
+ "module": "",
319
+ "names": "",
320
+ "dots": "",
321
+ "language_hint": ""
322
+ },
323
+ "id": "bca733b8c9e46c9d"
324
+ },
325
+ {
326
+ "blob_sha": "sha1",
327
+ "file_path": "javascript.js",
328
+ "kind": "function",
329
+ "name": "recipients",
330
+ "scope": "",
331
+ "language": "javascript",
332
+ "file_language": "javascript",
333
+ "content": "function* recipients(names) {\n for (const name of names) {\n yield name;\n }\n}",
334
+ "line_start": 35,
335
+ "line_end": 39,
336
+ "metadata": {
337
+ "module": "",
338
+ "names": "",
339
+ "dots": "",
340
+ "language_hint": ""
341
+ },
342
+ "id": "9fe04be46ef4242e"
343
+ },
344
+ {
345
+ "blob_sha": "sha1",
346
+ "file_path": "javascript.js",
347
+ "kind": "comment",
348
+ "name": "",
349
+ "scope": "",
350
+ "language": "javascript",
351
+ "file_language": "javascript",
352
+ "content": "/** Greeting helpers grouped as an object literal. */",
353
+ "line_start": 41,
354
+ "line_end": 41,
355
+ "metadata": {
356
+ "module": "",
357
+ "names": "",
358
+ "dots": "",
359
+ "language_hint": ""
360
+ },
361
+ "id": "3107f6745e3b67a3"
362
+ },
363
+ {
364
+ "blob_sha": "sha1",
365
+ "file_path": "javascript.js",
366
+ "kind": "variable",
367
+ "name": "helpers",
368
+ "scope": "",
369
+ "language": "javascript",
370
+ "file_language": "javascript",
371
+ "content": "helpers = {\n shout(name) {\n return `${name.toUpperCase()}!`;\n },\n}",
372
+ "line_start": 42,
373
+ "line_end": 46,
374
+ "metadata": {
375
+ "module": "",
376
+ "names": "",
377
+ "dots": "",
378
+ "language_hint": ""
379
+ },
380
+ "id": "607e8ab8ea7c25fe"
381
+ },
382
+ {
383
+ "blob_sha": "sha1",
384
+ "file_path": "javascript.js",
385
+ "kind": "method",
386
+ "name": "shout",
387
+ "scope": "",
388
+ "language": "javascript",
389
+ "file_language": "javascript",
390
+ "content": "shout(name) {\n return `${name.toUpperCase()}!`;\n }",
391
+ "line_start": 43,
392
+ "line_end": 45,
393
+ "metadata": {
394
+ "module": "",
395
+ "names": "",
396
+ "dots": "",
397
+ "language_hint": ""
398
+ },
399
+ "id": "934493ecda7800b3"
400
+ },
401
+ {
402
+ "blob_sha": "sha1",
403
+ "file_path": "styles.css",
404
+ "kind": "class",
405
+ "name": ".greeter",
406
+ "scope": "",
407
+ "language": "css",
408
+ "file_language": "css",
409
+ "content": ".greeter {\n color: #333;\n}",
410
+ "line_start": 1,
411
+ "line_end": 3,
412
+ "metadata": {
413
+ "module": "",
414
+ "names": "",
415
+ "dots": "",
416
+ "language_hint": ""
417
+ },
418
+ "id": "6f956de079e5ad55"
419
+ }
420
+ ]