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.
- rbtr_lang_javascript/__init__.py +1 -0
- rbtr_lang_javascript/javascript.scm +4 -0
- rbtr_lang_javascript/plugin.py +190 -0
- rbtr_lang_javascript/py.typed +0 -0
- rbtr_lang_javascript/shared.scm +29 -0
- rbtr_lang_javascript/tests/__init__.py +0 -0
- rbtr_lang_javascript/tests/__snapshots__/test_samples/test_edges_match_snapshot[javascript].json +4 -0
- rbtr_lang_javascript/tests/__snapshots__/test_samples/test_edges_match_snapshot[tsx].json +3 -0
- rbtr_lang_javascript/tests/__snapshots__/test_samples/test_edges_match_snapshot[typescript].json +6 -0
- rbtr_lang_javascript/tests/__snapshots__/test_samples/test_extraction_matches_snapshot[javascript].json +420 -0
- rbtr_lang_javascript/tests/__snapshots__/test_samples/test_extraction_matches_snapshot[tsx].json +211 -0
- rbtr_lang_javascript/tests/__snapshots__/test_samples/test_extraction_matches_snapshot[typescript].json +610 -0
- rbtr_lang_javascript/tests/cases_docstrings.py +225 -0
- rbtr_lang_javascript/tests/cases_extraction.py +429 -0
- rbtr_lang_javascript/tests/samples/javascript/config.js +2 -0
- rbtr_lang_javascript/tests/samples/javascript/javascript.js +46 -0
- rbtr_lang_javascript/tests/samples/javascript/styles.css +3 -0
- rbtr_lang_javascript/tests/samples/tsx/labels.ts +2 -0
- rbtr_lang_javascript/tests/samples/tsx/tsx.tsx +33 -0
- rbtr_lang_javascript/tests/samples/typescript/config.ts +2 -0
- rbtr_lang_javascript/tests/samples/typescript/types.ts +2 -0
- rbtr_lang_javascript/tests/samples/typescript/typescript.ts +68 -0
- rbtr_lang_javascript/tests/test_docstrings.py +51 -0
- rbtr_lang_javascript/tests/test_extraction.py +80 -0
- rbtr_lang_javascript/tests/test_samples.py +95 -0
- rbtr_lang_javascript/typescript.scm +36 -0
- rbtr_lang_javascript/variables.scm +54 -0
- rbtr_lang_javascript-2026.9.0.dev0.dist-info/METADATA +74 -0
- rbtr_lang_javascript-2026.9.0.dev0.dist-info/RECORD +32 -0
- rbtr_lang_javascript-2026.9.0.dev0.dist-info/WHEEL +4 -0
- rbtr_lang_javascript-2026.9.0.dev0.dist-info/entry_points.txt +5 -0
- rbtr_lang_javascript-2026.9.0.dev0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"""JavaScript / TypeScript docstring-extraction test cases.
|
|
2
|
+
|
|
3
|
+
Each `@case` returns `(lang, source, symbol_name, snippet)` consumed by
|
|
4
|
+
`test_docstrings.py`; tags drive the documented/undocumented assertion.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from pytest_cases import case
|
|
10
|
+
|
|
11
|
+
type DocstringCase = tuple[str, str, str, str]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@case(tags=["documented", "canonical", "exterior_doc"])
|
|
15
|
+
def case_js_jsdoc_on_function() -> DocstringCase:
|
|
16
|
+
"""Canonical JSDoc above a function declaration."""
|
|
17
|
+
src = """\
|
|
18
|
+
/** Return a friendly greeting. */
|
|
19
|
+
function greet() {}
|
|
20
|
+
"""
|
|
21
|
+
return "javascript", src, "greet", "Return a friendly greeting"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@case(tags=["documented", "canonical", "exterior_doc"])
|
|
25
|
+
def case_js_jsdoc_on_class() -> DocstringCase:
|
|
26
|
+
"""Canonical JSDoc above a class declaration."""
|
|
27
|
+
src = """\
|
|
28
|
+
/** A widget. */
|
|
29
|
+
class Widget {}
|
|
30
|
+
"""
|
|
31
|
+
return "javascript", src, "Widget", "A widget"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@case(tags=["documented", "canonical", "exterior_doc"])
|
|
35
|
+
def case_js_jsdoc_on_arrow_function() -> DocstringCase:
|
|
36
|
+
"""Arrow-function assignment — the `@function` capture
|
|
37
|
+
lands on the `lexical_declaration`, so JSDoc attaches via
|
|
38
|
+
the walk on that node's prev_named_sibling.
|
|
39
|
+
"""
|
|
40
|
+
src = """\
|
|
41
|
+
/** Increment. */
|
|
42
|
+
const inc = (x) => x + 1;
|
|
43
|
+
"""
|
|
44
|
+
return "javascript", src, "inc", "Increment"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@case(tags=["documented", "edge_case", "exterior_doc"])
|
|
48
|
+
def case_js_multiline_jsdoc() -> DocstringCase:
|
|
49
|
+
"""Multi-line JSDoc with leading `*` gutter."""
|
|
50
|
+
src = """\
|
|
51
|
+
/**
|
|
52
|
+
* Compute a checksum over *data*.
|
|
53
|
+
*
|
|
54
|
+
* The algorithm is CRC32 — explained below.
|
|
55
|
+
*/
|
|
56
|
+
function checksum(data) { return 0; }
|
|
57
|
+
"""
|
|
58
|
+
return "javascript", src, "checksum", "The algorithm is CRC32"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@case(tags=["documented", "edge_case", "exterior_doc"])
|
|
62
|
+
def case_js_banner_comment() -> DocstringCase:
|
|
63
|
+
"""`/*! ... */` banner comments are common in bundled UMD
|
|
64
|
+
libs; the grammar lands them as `comment` nodes and we
|
|
65
|
+
attach them — the benchmark will say whether that hurts.
|
|
66
|
+
"""
|
|
67
|
+
src = """\
|
|
68
|
+
/*! (c) 2024 Acme. */
|
|
69
|
+
function publicApi() {}
|
|
70
|
+
"""
|
|
71
|
+
return "javascript", src, "publicApi", "(c) 2024 Acme"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@case(tags=["documented", "unconventional", "exterior_doc"])
|
|
75
|
+
def case_js_line_comment_run() -> DocstringCase:
|
|
76
|
+
"""`//` comment runs used as docs — common in TS-first
|
|
77
|
+
code where JSDoc is syntactically less convenient.
|
|
78
|
+
"""
|
|
79
|
+
src = """\
|
|
80
|
+
// First description line.
|
|
81
|
+
// Second description line.
|
|
82
|
+
function documented() {}
|
|
83
|
+
"""
|
|
84
|
+
return "javascript", src, "documented", "First description line"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@case(tags=["undocumented", "no_docs"])
|
|
88
|
+
def case_js_function_without_doc() -> DocstringCase:
|
|
89
|
+
"""Plain function, no comments."""
|
|
90
|
+
src = """\
|
|
91
|
+
function bare() {}
|
|
92
|
+
"""
|
|
93
|
+
return "javascript", src, "bare", "PHANTOM_DOC_TEXT_SHOULD_NEVER_APPEAR"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@case(tags=["undocumented", "boundary_not_attached"])
|
|
97
|
+
def case_js_jsdoc_detached_by_blank_line() -> DocstringCase:
|
|
98
|
+
"""A blank line between the JSDoc block and the function
|
|
99
|
+
breaks attachment.
|
|
100
|
+
"""
|
|
101
|
+
src = """\
|
|
102
|
+
/** Stale JSDoc — not attached. */
|
|
103
|
+
|
|
104
|
+
function stale() {}
|
|
105
|
+
"""
|
|
106
|
+
return "javascript", src, "stale", "Stale JSDoc"
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@case(tags=["undocumented", "invalid"])
|
|
110
|
+
def case_js_jsdoc_above_import_does_not_attach_to_class() -> DocstringCase:
|
|
111
|
+
"""JSDoc above an `import` stays on the import line. A
|
|
112
|
+
class several statements later must *not* inherit it.
|
|
113
|
+
Imports are excluded from attachment by design (see
|
|
114
|
+
`treesitter.extract_symbols`).
|
|
115
|
+
"""
|
|
116
|
+
src = """\
|
|
117
|
+
/** Nonsense JSDoc above import. */
|
|
118
|
+
import { x } from './x';
|
|
119
|
+
|
|
120
|
+
class Real {}
|
|
121
|
+
"""
|
|
122
|
+
return "javascript", src, "Real", "Nonsense JSDoc"
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@case(tags=["documented", "canonical", "exterior_doc"])
|
|
126
|
+
def case_ts_jsdoc_on_function() -> DocstringCase:
|
|
127
|
+
"""Canonical JSDoc above a typed function declaration."""
|
|
128
|
+
src = """\
|
|
129
|
+
/** Return the length of *s*. */
|
|
130
|
+
function len(s: string): number { return s.length; }
|
|
131
|
+
"""
|
|
132
|
+
return "typescript", src, "len", "Return the length"
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@case(tags=["documented", "canonical", "exterior_doc"])
|
|
136
|
+
def case_ts_jsdoc_on_class() -> DocstringCase:
|
|
137
|
+
"""JSDoc above a TypeScript class — grammar uses
|
|
138
|
+
`type_identifier` for the class name.
|
|
139
|
+
"""
|
|
140
|
+
src = """\
|
|
141
|
+
/** A widget. */
|
|
142
|
+
class Widget {}
|
|
143
|
+
"""
|
|
144
|
+
return "typescript", src, "Widget", "A widget"
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@case(tags=["documented", "canonical", "exterior_doc"])
|
|
148
|
+
def case_ts_jsdoc_on_arrow_function() -> DocstringCase:
|
|
149
|
+
"""Arrow-function assignment with a type annotation."""
|
|
150
|
+
src = """\
|
|
151
|
+
/** Increment. */
|
|
152
|
+
const inc: (x: number) => number = (x) => x + 1;
|
|
153
|
+
"""
|
|
154
|
+
return "typescript", src, "inc", "Increment"
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@case(tags=["documented", "edge_case", "exterior_doc"])
|
|
158
|
+
def case_ts_jsdoc_on_generic_function() -> DocstringCase:
|
|
159
|
+
"""Generic type parameters between name and arguments."""
|
|
160
|
+
src = """\
|
|
161
|
+
/** Identity. */
|
|
162
|
+
function identity<T>(x: T): T { return x; }
|
|
163
|
+
"""
|
|
164
|
+
return "typescript", src, "identity", "Identity"
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@case(tags=["documented", "edge_case", "exterior_doc"])
|
|
168
|
+
def case_ts_multiline_jsdoc_with_tags() -> DocstringCase:
|
|
169
|
+
"""Multi-line JSDoc with `@param` / `@returns` tags."""
|
|
170
|
+
src = """\
|
|
171
|
+
/**
|
|
172
|
+
* Compute a hash.
|
|
173
|
+
*
|
|
174
|
+
* @param data bytes to hash
|
|
175
|
+
* @returns hex digest
|
|
176
|
+
*/
|
|
177
|
+
function hash(data: Uint8Array): string { return ''; }
|
|
178
|
+
"""
|
|
179
|
+
return "typescript", src, "hash", "@returns hex digest"
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@case(tags=["documented", "unconventional", "exterior_doc"])
|
|
183
|
+
def case_ts_line_comment_run() -> DocstringCase:
|
|
184
|
+
"""`//` comment runs used as docs, common in TS-heavy
|
|
185
|
+
codebases that avoid JSDoc because types are already in
|
|
186
|
+
the signature.
|
|
187
|
+
"""
|
|
188
|
+
src = """\
|
|
189
|
+
// Describe the value.
|
|
190
|
+
// Useful in calling code.
|
|
191
|
+
function describe(x: number): string { return String(x); }
|
|
192
|
+
"""
|
|
193
|
+
return "typescript", src, "describe", "Describe the value"
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@case(tags=["undocumented", "no_docs"])
|
|
197
|
+
def case_ts_function_without_doc() -> DocstringCase:
|
|
198
|
+
"""Plain TS function."""
|
|
199
|
+
src = """\
|
|
200
|
+
function bare(x: number): number { return x; }
|
|
201
|
+
"""
|
|
202
|
+
return "typescript", src, "bare", "PHANTOM_DOC_TEXT_SHOULD_NEVER_APPEAR"
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
@case(tags=["undocumented", "boundary_not_attached"])
|
|
206
|
+
def case_ts_jsdoc_detached_by_blank_line() -> DocstringCase:
|
|
207
|
+
"""Blank line breaks attachment for TS too."""
|
|
208
|
+
src = """\
|
|
209
|
+
/** Stale JSDoc. */
|
|
210
|
+
|
|
211
|
+
function stale(): void {}
|
|
212
|
+
"""
|
|
213
|
+
return "typescript", src, "stale", "Stale JSDoc"
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
@case(tags=["undocumented", "invalid"])
|
|
217
|
+
def case_ts_jsdoc_above_import() -> DocstringCase:
|
|
218
|
+
"""JSDoc above `import` does not attach to a later class."""
|
|
219
|
+
src = """\
|
|
220
|
+
/** Nonsense JSDoc. */
|
|
221
|
+
import { x } from './x';
|
|
222
|
+
|
|
223
|
+
class Real {}
|
|
224
|
+
"""
|
|
225
|
+
return "typescript", src, "Real", "Nonsense JSDoc"
|
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
"""JavaScript / TypeScript / TSX extraction test cases.
|
|
2
|
+
|
|
3
|
+
Each `@case` returns test data consumed by `test_extraction.py` via
|
|
4
|
+
`pytest-cases`. See the plugin docstring for the source→chunk mapping.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
from pytest_cases import case
|
|
11
|
+
|
|
12
|
+
type SymbolCase = tuple[str, str, list[tuple[str, str, str]]]
|
|
13
|
+
type ImportCase = tuple[str, str, dict[str, str]]
|
|
14
|
+
type MultiImportCase = tuple[str, str, int, list[dict[str, str]]]
|
|
15
|
+
type MixedCase = tuple[str, str, set[str], list[tuple[str, str]]]
|
|
16
|
+
|
|
17
|
+
_xfail_nested = pytest.mark.xfail(
|
|
18
|
+
reason="nested/chained destructuring unsupported — no query-only recursion",
|
|
19
|
+
strict=True,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@case(tags=["symbol"])
|
|
24
|
+
def case_js_function_declaration() -> SymbolCase:
|
|
25
|
+
"""function greet() {}."""
|
|
26
|
+
return "javascript", "function greet() {}\n", [("function", "greet", "")]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@case(tags=["symbol"])
|
|
30
|
+
def case_js_arrow_function() -> SymbolCase:
|
|
31
|
+
"""const add = (a, b) => a + b."""
|
|
32
|
+
return "javascript", "const add = (a, b) => a + b;\n", [("function", "add", "")]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@case(tags=["symbol"])
|
|
36
|
+
def case_js_arrow_function_block() -> SymbolCase:
|
|
37
|
+
"""Arrow function with block body."""
|
|
38
|
+
src = """\
|
|
39
|
+
const fetch = () => {
|
|
40
|
+
return data;
|
|
41
|
+
};
|
|
42
|
+
"""
|
|
43
|
+
return "javascript", src, [("function", "fetch", "")]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@case(tags=["symbol"])
|
|
47
|
+
def case_js_multiple_functions() -> SymbolCase:
|
|
48
|
+
"""Multiple function forms."""
|
|
49
|
+
src = """\
|
|
50
|
+
function a() {}
|
|
51
|
+
function b() {}
|
|
52
|
+
const c = () => {};
|
|
53
|
+
"""
|
|
54
|
+
return (
|
|
55
|
+
"javascript",
|
|
56
|
+
src,
|
|
57
|
+
[
|
|
58
|
+
("function", "a", ""),
|
|
59
|
+
("function", "b", ""),
|
|
60
|
+
("function", "c", ""),
|
|
61
|
+
],
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@case(tags=["symbol"])
|
|
66
|
+
def case_js_class() -> SymbolCase:
|
|
67
|
+
"""class User {}."""
|
|
68
|
+
return "javascript", "class User {}\n", [("class", "User", "")]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@case(tags=["symbol"])
|
|
72
|
+
def case_js_class_extends() -> SymbolCase:
|
|
73
|
+
"""class Admin extends User {}."""
|
|
74
|
+
return "javascript", "class Admin extends User {}\n", [("class", "Admin", "")]
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@case(tags=["symbol"])
|
|
78
|
+
def case_js_nested_function() -> SymbolCase:
|
|
79
|
+
"""A function nested in a function is addressed by the outer function."""
|
|
80
|
+
src = """\
|
|
81
|
+
function outer() {
|
|
82
|
+
function inner() {
|
|
83
|
+
return 1;
|
|
84
|
+
}
|
|
85
|
+
return inner;
|
|
86
|
+
}
|
|
87
|
+
"""
|
|
88
|
+
return "javascript", src, [("function", "inner", "outer")]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@case(tags=["import"])
|
|
92
|
+
def case_js_import_named_single() -> ImportCase:
|
|
93
|
+
"""import { foo } from './models'."""
|
|
94
|
+
return (
|
|
95
|
+
"javascript",
|
|
96
|
+
"import { foo } from './models';\n",
|
|
97
|
+
{"module": "models", "names": "foo", "dots": "1"},
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@case(tags=["import"])
|
|
102
|
+
def case_js_import_named_multiple() -> ImportCase:
|
|
103
|
+
"""import { foo, bar } from './models'."""
|
|
104
|
+
return (
|
|
105
|
+
"javascript",
|
|
106
|
+
"import { foo, bar } from './models';\n",
|
|
107
|
+
{"module": "models", "names": "foo,bar", "dots": "1"},
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@case(tags=["import"])
|
|
112
|
+
def case_js_import_named_parent() -> ImportCase:
|
|
113
|
+
"""import from parent directory."""
|
|
114
|
+
return (
|
|
115
|
+
"javascript",
|
|
116
|
+
"import { Config } from '../config';\n",
|
|
117
|
+
{"module": "config", "names": "Config", "dots": "2"},
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@case(tags=["import"])
|
|
122
|
+
def case_js_import_named_grandparent() -> ImportCase:
|
|
123
|
+
"""import from grandparent directory."""
|
|
124
|
+
return (
|
|
125
|
+
"javascript",
|
|
126
|
+
"import { util } from '../../shared/util';\n",
|
|
127
|
+
{"module": "shared/util", "names": "util", "dots": "3"},
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@case(tags=["import"])
|
|
132
|
+
def case_js_import_default() -> ImportCase:
|
|
133
|
+
"""import React from 'react'."""
|
|
134
|
+
return "javascript", "import React from 'react';\n", {"module": "react", "names": "React"}
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@case(tags=["import"])
|
|
138
|
+
def case_js_import_default_relative() -> ImportCase:
|
|
139
|
+
"""import App from './App'."""
|
|
140
|
+
return (
|
|
141
|
+
"javascript",
|
|
142
|
+
"import App from './App';\n",
|
|
143
|
+
{"module": "App", "names": "App", "dots": "1"},
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@case(tags=["import"])
|
|
148
|
+
def case_js_import_namespace() -> ImportCase:
|
|
149
|
+
"""import * as utils from '../utils'."""
|
|
150
|
+
return (
|
|
151
|
+
"javascript",
|
|
152
|
+
"import * as utils from '../utils';\n",
|
|
153
|
+
{"module": "utils", "names": "utils", "dots": "2"},
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@case(tags=["import"])
|
|
158
|
+
def case_js_import_side_effect() -> ImportCase:
|
|
159
|
+
"""import './styles.css' — side-effect only."""
|
|
160
|
+
return "javascript", "import './styles.css';\n", {"module": "styles", "dots": "1"}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@case(tags=["import"])
|
|
164
|
+
def case_js_import_side_effect_no_ext() -> ImportCase:
|
|
165
|
+
"""import './polyfills' — no extension."""
|
|
166
|
+
return "javascript", "import './polyfills';\n", {"module": "polyfills", "dots": "1"}
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@case(tags=["import"])
|
|
170
|
+
def case_js_import_package() -> ImportCase:
|
|
171
|
+
"""import express from 'express' — absolute."""
|
|
172
|
+
return (
|
|
173
|
+
"javascript",
|
|
174
|
+
"import express from 'express';\n",
|
|
175
|
+
{"module": "express", "names": "express"},
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@case(tags=["import"])
|
|
180
|
+
def case_js_import_scoped_package() -> ImportCase:
|
|
181
|
+
"""import from scoped npm package."""
|
|
182
|
+
return (
|
|
183
|
+
"javascript",
|
|
184
|
+
"import { render } from '@testing-library/react';\n",
|
|
185
|
+
{"module": "@testing-library/react", "names": "render"},
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@case(tags=["multi_import"])
|
|
190
|
+
def case_js_multiple_imports() -> MultiImportCase:
|
|
191
|
+
"""Two import statements."""
|
|
192
|
+
src = """\
|
|
193
|
+
import React from 'react';
|
|
194
|
+
import { useState } from 'react';
|
|
195
|
+
"""
|
|
196
|
+
return (
|
|
197
|
+
"javascript",
|
|
198
|
+
src,
|
|
199
|
+
2,
|
|
200
|
+
[
|
|
201
|
+
{"module": "react", "names": "React"},
|
|
202
|
+
{"module": "react", "names": "useState"},
|
|
203
|
+
],
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@case(tags=["mixed"])
|
|
208
|
+
def case_js_full_module() -> MixedCase:
|
|
209
|
+
"""Realistic JS module with JSDoc on every symbol.
|
|
210
|
+
|
|
211
|
+
Adding JSDoc here exercises the production-plugin default
|
|
212
|
+
(leading-comment attachment on) against the conventional
|
|
213
|
+
JS documentation style. The expected-kinds tuple is
|
|
214
|
+
unchanged — this confirms documentation does not disturb
|
|
215
|
+
symbol extraction. Content assertions for docstring
|
|
216
|
+
presence are covered by `test_docstrings.py`.
|
|
217
|
+
"""
|
|
218
|
+
src = """\
|
|
219
|
+
import { Model } from './model';
|
|
220
|
+
|
|
221
|
+
/** Service facade over `Model`. */
|
|
222
|
+
class Service {
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Create a new service instance. */
|
|
226
|
+
function create() {
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** Tear it down. */
|
|
230
|
+
const destroy = () => {};
|
|
231
|
+
"""
|
|
232
|
+
return "javascript", src, {"import", "class", "function"}, []
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
@case(tags=["symbol"])
|
|
236
|
+
def case_ts_function() -> SymbolCase:
|
|
237
|
+
"""function greet(): void {}."""
|
|
238
|
+
return "typescript", "function greet(): void {}\n", [("function", "greet", "")]
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
@case(tags=["symbol"])
|
|
242
|
+
def case_ts_arrow_function() -> SymbolCase:
|
|
243
|
+
"""Arrow function with type annotations."""
|
|
244
|
+
return (
|
|
245
|
+
"typescript",
|
|
246
|
+
"const add = (a: number, b: number): number => a + b;\n",
|
|
247
|
+
[("function", "add", "")],
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
@case(tags=["symbol"])
|
|
252
|
+
def case_ts_class() -> SymbolCase:
|
|
253
|
+
"""class Service {}."""
|
|
254
|
+
return "typescript", "class Service {}\n", [("class", "Service", "")]
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@case(tags=["symbol"])
|
|
258
|
+
def case_ts_class_generics() -> SymbolCase:
|
|
259
|
+
"""class Container<T> {}."""
|
|
260
|
+
return "typescript", "class Container<T> {}\n", [("class", "Container", "")]
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
@case(tags=["symbol"])
|
|
264
|
+
def case_ts_namespace_function() -> SymbolCase:
|
|
265
|
+
"""A function in a TS namespace is addressed by the namespace.
|
|
266
|
+
|
|
267
|
+
TS `namespace` is not tracked today (`f` → ""); target "N".
|
|
268
|
+
"""
|
|
269
|
+
src = """\
|
|
270
|
+
namespace N {
|
|
271
|
+
export function f(): void {}
|
|
272
|
+
}
|
|
273
|
+
"""
|
|
274
|
+
return "typescript", src, [("function", "f", "N")]
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
@case(tags=["symbol"])
|
|
278
|
+
def case_ts_nested_namespace() -> SymbolCase:
|
|
279
|
+
"""Nested TS namespaces compose."""
|
|
280
|
+
src = """\
|
|
281
|
+
namespace A {
|
|
282
|
+
export namespace B {
|
|
283
|
+
export function f(): void {}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
"""
|
|
287
|
+
return "typescript", src, [("function", "f", "A::B")]
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
@case(tags=["import"])
|
|
291
|
+
def case_ts_import_named() -> ImportCase:
|
|
292
|
+
"""import { User } from './types'."""
|
|
293
|
+
return (
|
|
294
|
+
"typescript",
|
|
295
|
+
"import { User } from './types';\n",
|
|
296
|
+
{"module": "types", "names": "User", "dots": "1"},
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
@case(tags=["import"])
|
|
301
|
+
def case_ts_import_type() -> ImportCase:
|
|
302
|
+
"""import type { Config } from './config'."""
|
|
303
|
+
return (
|
|
304
|
+
"typescript",
|
|
305
|
+
"import type { Config } from './config';\n",
|
|
306
|
+
{"module": "config", "names": "Config", "dots": "1"},
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
@case(tags=["import"])
|
|
311
|
+
def case_ts_import_default() -> ImportCase:
|
|
312
|
+
"""import Express from 'express'."""
|
|
313
|
+
return (
|
|
314
|
+
"typescript",
|
|
315
|
+
"import Express from 'express';\n",
|
|
316
|
+
{"module": "express", "names": "Express"},
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
@case(tags=["import"])
|
|
321
|
+
def case_ts_import_namespace() -> ImportCase:
|
|
322
|
+
"""import * as path from 'path'."""
|
|
323
|
+
return "typescript", "import * as path from 'path';\n", {"module": "path", "names": "path"}
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
@case(tags=["import"])
|
|
327
|
+
def case_ts_import_side_effect() -> ImportCase:
|
|
328
|
+
"""import './setup'."""
|
|
329
|
+
return "typescript", "import './setup';\n", {"module": "setup", "dots": "1"}
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
@case(tags=["mixed"])
|
|
333
|
+
def case_ts_full_module() -> MixedCase:
|
|
334
|
+
"""Realistic TS module with JSDoc on every symbol.
|
|
335
|
+
|
|
336
|
+
Mirrors the JS variant; expected-kinds tuple unchanged.
|
|
337
|
+
Content assertions for docstring presence are in
|
|
338
|
+
`test_docstrings.py`.
|
|
339
|
+
"""
|
|
340
|
+
src = """\
|
|
341
|
+
import { Model } from './model';
|
|
342
|
+
|
|
343
|
+
/** Repository over `Model` records. */
|
|
344
|
+
class Repository {
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/** Run a query and return the first row. */
|
|
348
|
+
function query(): void {
|
|
349
|
+
}
|
|
350
|
+
"""
|
|
351
|
+
return "typescript", src, {"import", "class", "function"}, []
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
@case(tags=["symbol"])
|
|
355
|
+
def case_js_module_const() -> SymbolCase:
|
|
356
|
+
"""Top-level const."""
|
|
357
|
+
return "javascript", "const MAX = 100;\n", [("variable", "MAX", "")]
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
@case(tags=["symbol"])
|
|
361
|
+
def case_js_exported_const() -> SymbolCase:
|
|
362
|
+
"""Exported top-level const."""
|
|
363
|
+
return "javascript", "export const TIMEOUT = 30;\n", [("variable", "TIMEOUT", "")]
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
@case(tags=["symbol"])
|
|
367
|
+
def case_ts_module_const() -> SymbolCase:
|
|
368
|
+
"""Top-level annotated const."""
|
|
369
|
+
return "typescript", "const MAX: number = 100;\n", [("variable", "MAX", "")]
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
@case(tags=["symbol"])
|
|
373
|
+
def case_js_object_destructure() -> SymbolCase:
|
|
374
|
+
"""Object destructuring (shorthand)."""
|
|
375
|
+
return "javascript", "const {a, b} = o;\n", [("variable", "a", ""), ("variable", "b", "")]
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
@case(tags=["symbol"])
|
|
379
|
+
def case_js_object_renamed() -> SymbolCase:
|
|
380
|
+
"""Object destructuring with rename binds the renamed target."""
|
|
381
|
+
return "javascript", "const {a: ra} = o;\n", [("variable", "ra", "")]
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
@case(tags=["symbol"])
|
|
385
|
+
def case_js_array_destructure() -> SymbolCase:
|
|
386
|
+
"""Array destructuring."""
|
|
387
|
+
return "javascript", "const [x, y] = arr;\n", [("variable", "x", ""), ("variable", "y", "")]
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
@case(tags=["symbol"])
|
|
391
|
+
def case_js_object_rest() -> SymbolCase:
|
|
392
|
+
"""Object rest element."""
|
|
393
|
+
return (
|
|
394
|
+
"javascript",
|
|
395
|
+
"const {a, ...rest} = o;\n",
|
|
396
|
+
[("variable", "a", ""), ("variable", "rest", "")],
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
@case(tags=["symbol"])
|
|
401
|
+
def case_js_exported_destructure() -> SymbolCase:
|
|
402
|
+
"""Exported destructuring."""
|
|
403
|
+
return (
|
|
404
|
+
"javascript",
|
|
405
|
+
"export const {a, b} = o;\n",
|
|
406
|
+
[("variable", "a", ""), ("variable", "b", "")],
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
@case(tags=["symbol"])
|
|
411
|
+
def case_ts_object_destructure() -> SymbolCase:
|
|
412
|
+
"""TS object destructuring."""
|
|
413
|
+
return "typescript", "const {a, b} = o;\n", [("variable", "a", ""), ("variable", "b", "")]
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
@case(tags=["symbol"], marks=_xfail_nested)
|
|
417
|
+
def case_js_nested_array_xfail() -> SymbolCase:
|
|
418
|
+
"""Nested array destructuring — only the outer level captured today."""
|
|
419
|
+
return (
|
|
420
|
+
"javascript",
|
|
421
|
+
"const [a, [b, c]] = x;\n",
|
|
422
|
+
[("variable", "a", ""), ("variable", "b", ""), ("variable", "c", "")],
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
@case(tags=["symbol"], marks=_xfail_nested)
|
|
427
|
+
def case_js_nested_object_xfail() -> SymbolCase:
|
|
428
|
+
"""Nested object destructuring — nothing captured today."""
|
|
429
|
+
return "javascript", "const {a: {b}} = x;\n", [("variable", "b", "")]
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Greeter — format greetings for named recipients.
|
|
2
|
+
//
|
|
3
|
+
// The JavaScript plugin extracts function declarations, generator
|
|
4
|
+
// functions, arrow functions bound to consts, classes, module-level
|
|
5
|
+
// const/let variables (including destructuring), imports, and methods
|
|
6
|
+
// (class members, including get/set accessors; object-literal methods are
|
|
7
|
+
// captured without a scope).
|
|
8
|
+
|
|
9
|
+
import { LOCALE } from "./config.js";
|
|
10
|
+
import format from "formatter";
|
|
11
|
+
import "./styles.css";
|
|
12
|
+
|
|
13
|
+
export const DEFAULT_GREETING = "Hello";
|
|
14
|
+
const { locale, fallback } = LOCALE;
|
|
15
|
+
|
|
16
|
+
/** Format a greeting for a name. */
|
|
17
|
+
export function formatGreeting(name) {
|
|
18
|
+
return `${DEFAULT_GREETING}, ${name} (${locale ?? fallback})`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const cachedDefault = () => DEFAULT_GREETING;
|
|
22
|
+
|
|
23
|
+
/** Stateful greeter holding a prefix. */
|
|
24
|
+
export class Greeter {
|
|
25
|
+
constructor(prefix = DEFAULT_GREETING) {
|
|
26
|
+
this.prefix = prefix;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
greet(name) {
|
|
30
|
+
return format(`${this.prefix}, ${name}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Yield each recipient name in turn. */
|
|
35
|
+
export function* recipients(names) {
|
|
36
|
+
for (const name of names) {
|
|
37
|
+
yield name;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Greeting helpers grouped as an object literal. */
|
|
42
|
+
export const helpers = {
|
|
43
|
+
shout(name) {
|
|
44
|
+
return `${name.toUpperCase()}!`;
|
|
45
|
+
},
|
|
46
|
+
};
|