universal-ast-mapper 0.5.2

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.
@@ -0,0 +1,54 @@
1
+ export function lineRange(node) {
2
+ return { startLine: node.startPosition.row + 1, endLine: node.endPosition.row + 1 };
3
+ }
4
+ /** True if the identifier begins with an uppercase letter (Go export rule). */
5
+ export function startsUpper(name) {
6
+ const c = name[0];
7
+ return !!c && c !== c.toLowerCase() && c === c.toUpperCase();
8
+ }
9
+ /** Python convention: a single leading underscore (but not dunder) means private. */
10
+ export function pythonVisibility(name) {
11
+ if (name.startsWith("__") && name.endsWith("__"))
12
+ return "public"; // dunder
13
+ return name.startsWith("_") ? "private" : "public";
14
+ }
15
+ export function makeSymbol(init) {
16
+ const sym = {
17
+ name: init.name,
18
+ kind: init.kind,
19
+ visibility: init.visibility ?? "public",
20
+ range: lineRange(init.node),
21
+ children: init.children ?? [],
22
+ };
23
+ if (init.rawKind !== undefined)
24
+ sym.rawKind = init.rawKind;
25
+ if (init.signature !== undefined)
26
+ sym.signature = init.signature;
27
+ if (init.exported !== undefined)
28
+ sym.exported = init.exported;
29
+ if (init.doc !== undefined)
30
+ sym.doc = init.doc;
31
+ return sym;
32
+ }
33
+ /** Count a symbol tree, including nested children. */
34
+ export function countSymbols(symbols) {
35
+ let n = 0;
36
+ for (const s of symbols)
37
+ n += 1 + countSymbols(s.children);
38
+ return n;
39
+ }
40
+ /** Strip signature/doc/rawKind for the compact "outline" detail level. */
41
+ export function toOutline(symbols) {
42
+ return symbols.map((s) => {
43
+ const out = {
44
+ name: s.name,
45
+ kind: s.kind,
46
+ visibility: s.visibility,
47
+ range: s.range,
48
+ children: toOutline(s.children),
49
+ };
50
+ if (s.exported !== undefined)
51
+ out.exported = s.exported;
52
+ return out;
53
+ });
54
+ }
@@ -0,0 +1,212 @@
1
+ import { namedChildren, nameOf, headerSignature, leadingComment } from "../parser.js";
2
+ import { makeSymbol, startsUpper } from "./common.js";
3
+ // ─── Import extraction ────────────────────────────────────────────────────────
4
+ export function extractImportsGo(root, _source) {
5
+ const imports = [];
6
+ for (const child of namedChildren(root)) {
7
+ if (child.type === "import_declaration")
8
+ parseGoImportDecl(child, imports);
9
+ }
10
+ return imports;
11
+ }
12
+ function parseGoImportDecl(node, out) {
13
+ for (let i = 0; i < node.namedChildCount; i++) {
14
+ const c = node.namedChild(i);
15
+ if (!c)
16
+ continue;
17
+ if (c.type === "import_spec")
18
+ parseGoImportSpec(c, out);
19
+ else if (c.type === "import_spec_list") {
20
+ for (let j = 0; j < c.namedChildCount; j++) {
21
+ const spec = c.namedChild(j);
22
+ if (spec && spec.type === "import_spec")
23
+ parseGoImportSpec(spec, out);
24
+ }
25
+ }
26
+ }
27
+ }
28
+ function parseGoImportSpec(spec, out) {
29
+ const pathNode = spec.childForFieldName("path");
30
+ const nameNode = spec.childForFieldName("name");
31
+ if (!pathNode)
32
+ return;
33
+ const from = pathNode.text.replace(/^['"`]|['"`]$/g, "");
34
+ const pkgName = from.split("/").pop() ?? from;
35
+ if (!nameNode) {
36
+ out.push({ symbol: pkgName, from });
37
+ }
38
+ else if (nameNode.text === ".") {
39
+ out.push({ symbol: ".", from, isNamespaceImport: true });
40
+ }
41
+ else if (nameNode.text === "_") {
42
+ out.push({ symbol: "_", from, isSideEffect: true });
43
+ }
44
+ else {
45
+ out.push({ symbol: pkgName, from, alias: nameNode.text });
46
+ }
47
+ }
48
+ // ─── Symbol extraction ────────────────────────────────────────────────────────
49
+ export function extractGo(root, _source) {
50
+ const out = [];
51
+ for (const n of namedChildren(root)) {
52
+ const res = handle(n);
53
+ if (Array.isArray(res))
54
+ out.push(...res);
55
+ else if (res)
56
+ out.push(res);
57
+ }
58
+ return out;
59
+ }
60
+ function handle(node) {
61
+ switch (node.type) {
62
+ case "function_declaration": {
63
+ const name = nameOf(node) ?? "(func)";
64
+ return makeSymbol({
65
+ name,
66
+ kind: "function",
67
+ node,
68
+ rawKind: node.type,
69
+ signature: headerSignature(node, node.childForFieldName("body")),
70
+ visibility: startsUpper(name) ? "public" : "private",
71
+ exported: startsUpper(name),
72
+ doc: leadingComment(node),
73
+ });
74
+ }
75
+ case "method_declaration": {
76
+ const name = nameOf(node) ?? "(method)";
77
+ return makeSymbol({
78
+ name,
79
+ kind: "method",
80
+ node,
81
+ rawKind: node.type,
82
+ signature: headerSignature(node, node.childForFieldName("body")),
83
+ visibility: startsUpper(name) ? "public" : "private",
84
+ exported: startsUpper(name),
85
+ doc: leadingComment(node),
86
+ });
87
+ }
88
+ case "type_declaration": {
89
+ const out = [];
90
+ for (const spec of namedChildren(node)) {
91
+ if (spec.type === "type_spec" || spec.type === "type_alias") {
92
+ const sym = fromTypeSpec(spec, node);
93
+ if (sym)
94
+ out.push(sym);
95
+ }
96
+ }
97
+ return out;
98
+ }
99
+ case "const_declaration":
100
+ return fromValueSpecs(node, "const");
101
+ case "var_declaration":
102
+ return fromValueSpecs(node, "var");
103
+ default:
104
+ return null;
105
+ }
106
+ }
107
+ function fromTypeSpec(spec, declNode) {
108
+ const name = nameOf(spec);
109
+ if (!name)
110
+ return null;
111
+ const typeNode = spec.childForFieldName("type");
112
+ const exported = startsUpper(name);
113
+ const visibility = exported ? "public" : "private";
114
+ const doc = leadingComment(declNode);
115
+ if (typeNode && typeNode.type === "struct_type") {
116
+ return makeSymbol({
117
+ name,
118
+ kind: "struct",
119
+ node: spec,
120
+ rawKind: spec.type,
121
+ visibility,
122
+ exported,
123
+ doc,
124
+ children: structFields(typeNode),
125
+ });
126
+ }
127
+ if (typeNode && typeNode.type === "interface_type") {
128
+ return makeSymbol({
129
+ name,
130
+ kind: "interface",
131
+ node: spec,
132
+ rawKind: spec.type,
133
+ visibility,
134
+ exported,
135
+ doc,
136
+ children: interfaceMethods(typeNode),
137
+ });
138
+ }
139
+ return makeSymbol({
140
+ name,
141
+ kind: "type",
142
+ node: spec,
143
+ rawKind: spec.type,
144
+ signature: headerSignature(spec, null),
145
+ visibility,
146
+ exported,
147
+ doc,
148
+ });
149
+ }
150
+ function structFields(structType) {
151
+ const list = namedChildren(structType).find((c) => c.type === "field_declaration_list");
152
+ if (!list)
153
+ return [];
154
+ const out = [];
155
+ for (const field of namedChildren(list)) {
156
+ if (field.type !== "field_declaration")
157
+ continue;
158
+ const name = nameOf(field);
159
+ if (!name)
160
+ continue; // skip embedded fields
161
+ out.push(makeSymbol({
162
+ name,
163
+ kind: "field",
164
+ node: field,
165
+ rawKind: field.type,
166
+ signature: field.text.replace(/\s+/g, " ").trim(),
167
+ visibility: startsUpper(name) ? "public" : "private",
168
+ exported: startsUpper(name),
169
+ }));
170
+ }
171
+ return out;
172
+ }
173
+ function interfaceMethods(interfaceType) {
174
+ const out = [];
175
+ for (const m of namedChildren(interfaceType)) {
176
+ if (m.type !== "method_spec" && m.type !== "method_elem")
177
+ continue;
178
+ const name = nameOf(m);
179
+ if (!name)
180
+ continue;
181
+ out.push(makeSymbol({
182
+ name,
183
+ kind: "method",
184
+ node: m,
185
+ rawKind: m.type,
186
+ signature: headerSignature(m, null),
187
+ visibility: startsUpper(name) ? "public" : "private",
188
+ exported: startsUpper(name),
189
+ }));
190
+ }
191
+ return out;
192
+ }
193
+ function fromValueSpecs(node, kind) {
194
+ const out = [];
195
+ for (const spec of namedChildren(node)) {
196
+ if (spec.type !== "const_spec" && spec.type !== "var_spec")
197
+ continue;
198
+ const name = nameOf(spec);
199
+ if (!name)
200
+ continue;
201
+ out.push(makeSymbol({
202
+ name,
203
+ kind,
204
+ node: spec,
205
+ rawKind: spec.type,
206
+ signature: spec.text.replace(/\s+/g, " ").trim(),
207
+ visibility: startsUpper(name) ? "public" : "private",
208
+ exported: startsUpper(name),
209
+ }));
210
+ }
211
+ return out;
212
+ }
@@ -0,0 +1,142 @@
1
+ import { namedChildren, nameOf, headerSignature } from "../parser.js";
2
+ import { makeSymbol, pythonVisibility } from "./common.js";
3
+ export function extractPython(root, _source) {
4
+ return collect(namedChildren(root), false);
5
+ }
6
+ function collect(nodes, insideClass) {
7
+ const out = [];
8
+ for (const n of nodes) {
9
+ const res = handle(n, insideClass);
10
+ if (res)
11
+ out.push(res);
12
+ }
13
+ return out;
14
+ }
15
+ function handle(node, insideClass) {
16
+ if (node.type === "decorated_definition") {
17
+ const inner = innerDefinition(node);
18
+ return inner ? handle(inner, insideClass) : null;
19
+ }
20
+ if (node.type === "class_definition") {
21
+ const name = nameOf(node) ?? "(class)";
22
+ const body = node.childForFieldName("body");
23
+ const children = body ? collect(namedChildren(body), true) : [];
24
+ return makeSymbol({
25
+ name,
26
+ kind: "class",
27
+ node,
28
+ rawKind: node.type,
29
+ visibility: pythonVisibility(name),
30
+ exported: pythonVisibility(name) === "public",
31
+ doc: body ? docstring(body) : null,
32
+ children,
33
+ });
34
+ }
35
+ if (node.type === "function_definition") {
36
+ const name = nameOf(node) ?? "(function)";
37
+ const body = node.childForFieldName("body");
38
+ return makeSymbol({
39
+ name,
40
+ kind: insideClass ? "method" : "function",
41
+ node,
42
+ rawKind: node.type,
43
+ signature: headerSignature(node, body),
44
+ visibility: pythonVisibility(name),
45
+ exported: pythonVisibility(name) === "public",
46
+ doc: body ? docstring(body) : null,
47
+ });
48
+ }
49
+ return null;
50
+ }
51
+ function innerDefinition(decorated) {
52
+ const byField = decorated.childForFieldName("definition");
53
+ if (byField)
54
+ return byField;
55
+ for (const c of namedChildren(decorated)) {
56
+ if (c.type === "function_definition" || c.type === "class_definition")
57
+ return c;
58
+ }
59
+ return null;
60
+ }
61
+ // ─── Import extraction ────────────────────────────────────────────────────────
62
+ export function extractImportsPython(root, _source) {
63
+ const imports = [];
64
+ for (const child of namedChildren(root)) {
65
+ if (child.type === "import_statement")
66
+ parseSimpleImport(child, imports);
67
+ else if (child.type === "import_from_statement")
68
+ parseFromImport(child, imports);
69
+ }
70
+ return imports;
71
+ }
72
+ function parseSimpleImport(node, out) {
73
+ for (let i = 0; i < node.namedChildCount; i++) {
74
+ const c = node.namedChild(i);
75
+ if (!c)
76
+ continue;
77
+ if (c.type === "dotted_name" || c.type === "identifier") {
78
+ out.push({ symbol: c.text, from: c.text });
79
+ }
80
+ else if (c.type === "aliased_import") {
81
+ const nameNode = c.childForFieldName("name");
82
+ const aliasNode = c.childForFieldName("alias");
83
+ if (nameNode) {
84
+ const imp = { symbol: nameNode.text, from: nameNode.text };
85
+ if (aliasNode)
86
+ imp.alias = aliasNode.text;
87
+ out.push(imp);
88
+ }
89
+ }
90
+ }
91
+ }
92
+ /**
93
+ * Convert Python import path to a JS-style relative path.
94
+ * ".models" → "./models", "..utils" → "../utils", "os" → "os" (external).
95
+ */
96
+ function pythonFromPath(raw) {
97
+ const m = raw.match(/^(\.+)(.*)/);
98
+ if (!m)
99
+ return raw; // absolute/external import
100
+ const dotCount = m[1].length;
101
+ const rest = m[2]; // module name after the dots, e.g. "models" or ""
102
+ const upDirs = dotCount === 1 ? "." : Array(dotCount - 1).fill("..").join("/");
103
+ return rest ? `${upDirs}/${rest.replace(/\./g, "/")}` : upDirs;
104
+ }
105
+ function parseFromImport(node, out) {
106
+ const moduleNode = node.childForFieldName("module_name");
107
+ const from = moduleNode ? pythonFromPath(moduleNode.text) : ".";
108
+ for (let i = 0; i < node.namedChildCount; i++) {
109
+ const c = node.namedChild(i);
110
+ if (!c || c === moduleNode)
111
+ continue;
112
+ if (c.type === "wildcard_import") {
113
+ out.push({ symbol: "*", from, isNamespaceImport: true });
114
+ }
115
+ else if (c.type === "relative_import" || c.type === "dotted_name") {
116
+ // skip — these are part of the module path, not the imported names
117
+ }
118
+ else if (c.type === "identifier") {
119
+ out.push({ symbol: c.text, from });
120
+ }
121
+ else if (c.type === "aliased_import") {
122
+ const nameNode = c.childForFieldName("name");
123
+ const aliasNode = c.childForFieldName("alias");
124
+ if (nameNode) {
125
+ const imp = { symbol: nameNode.text, from };
126
+ if (aliasNode)
127
+ imp.alias = aliasNode.text;
128
+ out.push(imp);
129
+ }
130
+ }
131
+ }
132
+ }
133
+ /** Extract a leading triple-quoted docstring from a `block`. */
134
+ function docstring(body) {
135
+ const first = body.namedChild(0);
136
+ if (!first || first.type !== "expression_statement")
137
+ return null;
138
+ const str = first.namedChild(0);
139
+ if (!str || str.type !== "string")
140
+ return null;
141
+ return str.text.replace(/^[rbuRBU]*("""|'''|"|')/, "").replace(/("""|'''|"|')$/, "").trim().slice(0, 500);
142
+ }