universal-ast-mapper 1.2.0 → 1.4.0
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 +2 -0
- package/dist/extractors/typescript.js +79 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -605,6 +605,8 @@ Not part of the public API: the internal `src/` module layout and the generated
|
|
|
605
605
|
|
|
606
606
|
| Version | What changed |
|
|
607
607
|
|---------|--------------|
|
|
608
|
+
| **1.4.0** | **Dynamic import tracking** — dynamic `import("...")` and CommonJS `require("...")` calls (anywhere in a file) are now captured as imports with an `isDynamic` flag. Relative dynamic imports resolve and draw graph edges like static ones, so lazy-loaded routes/modules show up in the dependency graph. |
|
|
609
|
+
| **1.3.0** | **TS/JS decorators** — class and method symbols now carry a `decorators` field (`@Component({...})`, `@Injectable()`, `@Get("/x")`), in skeletons and `get_call_graph`. Extends the Python decorator support (v0.8.7) to TypeScript/JavaScript — traces Angular/NestJS-style framework wiring to its class/handler. |
|
|
608
610
|
| **1.2.0** | **File-level cross-package resolution** — in a monorepo, bare imports of a workspace package (`@org/utils`, `@org/utils/sub`) now resolve to the actual source file (preferring `src/` over built `dist/`), so `resolve_imports` marks them in-project and `build_symbol_graph` draws cross-package edges. Builds on the v1.1.0 workspace discovery. |
|
|
609
611
|
| **1.1.0** | **Monorepo support** — new `analyze_workspace` MCP tool + `ast-map workspace` (alias `ws`) CLI: discovers packages from npm/yarn `workspaces`, `pnpm-workspace.yaml`, or `lerna.json`, maps internal package dependencies, and flags circular package deps. **19 MCP tools**. |
|
|
610
612
|
| **1.0.0** | **Stable release.** Locks the public API (MCP tool names + schemas, CLI surface) for the 1.x line. Adds a **GitHub Action** (`action.yml`) to run `ast-map validate` as a CI architecture gate, plus a project CI workflow. Caps a 12-language engine with 18 MCP tools / 17 CLI commands spanning skeletons, dependency graphs, and deep analysis (dead code · cycles · impact · complexity · duplicates · unused params · decorators · type flow). |
|
|
@@ -47,7 +47,7 @@ function handle(node, exported, typeIndex) {
|
|
|
47
47
|
const name = nameOf(node) ?? "(anonymous class)";
|
|
48
48
|
const body = node.childForFieldName("body");
|
|
49
49
|
const children = body ? collect(namedChildren(body), false, typeIndex) : [];
|
|
50
|
-
|
|
50
|
+
const clsSym = makeSymbol({
|
|
51
51
|
name,
|
|
52
52
|
kind: "class",
|
|
53
53
|
node,
|
|
@@ -56,6 +56,8 @@ function handle(node, exported, typeIndex) {
|
|
|
56
56
|
doc: leadingComment(node),
|
|
57
57
|
children,
|
|
58
58
|
});
|
|
59
|
+
attachDecorators(clsSym, node);
|
|
60
|
+
return clsSym;
|
|
59
61
|
}
|
|
60
62
|
case "interface_declaration": {
|
|
61
63
|
const name = nameOf(node) ?? "(anonymous interface)";
|
|
@@ -114,7 +116,7 @@ function handle(node, exported, typeIndex) {
|
|
|
114
116
|
case "abstract_method_signature": {
|
|
115
117
|
const name = nameOf(node) ?? "(method)";
|
|
116
118
|
const body = node.childForFieldName("body");
|
|
117
|
-
|
|
119
|
+
const mSym = makeSymbol({
|
|
118
120
|
name,
|
|
119
121
|
kind: "method",
|
|
120
122
|
node,
|
|
@@ -123,6 +125,8 @@ function handle(node, exported, typeIndex) {
|
|
|
123
125
|
visibility: memberVisibility(node),
|
|
124
126
|
doc: leadingComment(node),
|
|
125
127
|
});
|
|
128
|
+
attachDecorators(mSym, node);
|
|
129
|
+
return mSym;
|
|
126
130
|
}
|
|
127
131
|
case "public_field_definition":
|
|
128
132
|
case "field_definition": {
|
|
@@ -209,8 +213,50 @@ export function extractImportsTS(root, _source) {
|
|
|
209
213
|
else if (child.type === "export_statement")
|
|
210
214
|
parseReExportStatement(child, imports);
|
|
211
215
|
}
|
|
216
|
+
collectDynamicImports(root, imports);
|
|
212
217
|
return imports;
|
|
213
218
|
}
|
|
219
|
+
/** First string-literal argument of a call's `arguments` node, or null. */
|
|
220
|
+
function firstStringArg(args) {
|
|
221
|
+
for (let i = 0; i < args.namedChildCount; i++) {
|
|
222
|
+
const a = args.namedChild(i);
|
|
223
|
+
if (a && a.type === "string") {
|
|
224
|
+
for (let j = 0; j < a.namedChildCount; j++) {
|
|
225
|
+
const frag = a.namedChild(j);
|
|
226
|
+
if (frag && frag.type === "string_fragment")
|
|
227
|
+
return frag.text;
|
|
228
|
+
}
|
|
229
|
+
return a.text.replace(/^['"`]|['"`]$/g, "");
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Walk the whole tree for dynamic `import("...")` and CommonJS `require("...")`
|
|
236
|
+
* calls (they can appear anywhere, not just at the top level). Only string-literal
|
|
237
|
+
* specifiers are captured; computed requires are skipped.
|
|
238
|
+
*/
|
|
239
|
+
function collectDynamicImports(node, out) {
|
|
240
|
+
if (node.type === "call_expression") {
|
|
241
|
+
const fn = node.childForFieldName("function");
|
|
242
|
+
const args = node.childForFieldName("arguments");
|
|
243
|
+
if (fn && args) {
|
|
244
|
+
const isImport = fn.type === "import";
|
|
245
|
+
const isRequire = fn.type === "identifier" && fn.text === "require";
|
|
246
|
+
if (isImport || isRequire) {
|
|
247
|
+
const spec = firstStringArg(args);
|
|
248
|
+
if (spec !== null) {
|
|
249
|
+
out.push({ symbol: "*", from: spec, isNamespaceImport: true, isDynamic: true });
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
255
|
+
const c = node.namedChild(i);
|
|
256
|
+
if (c)
|
|
257
|
+
collectDynamicImports(c, out);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
214
260
|
function parseReExportStatement(node, out) {
|
|
215
261
|
const source = extractModulePath(node.text);
|
|
216
262
|
if (!source)
|
|
@@ -454,3 +500,34 @@ function attachComponentInfo(sym, funcNode, declNode, name, idx) {
|
|
|
454
500
|
if (resolved)
|
|
455
501
|
sym.props = resolved;
|
|
456
502
|
}
|
|
503
|
+
// ─── TS/JS decorators ─────────────────────────────────────────────────────────
|
|
504
|
+
/** Strip the leading `@` and collapse whitespace from a decorator node. */
|
|
505
|
+
function decoratorText(node) {
|
|
506
|
+
return node.text.replace(/^@\s*/, "").replace(/\s+/g, " ").trim();
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Attach decorators to a class/method symbol. TS decorators appear either as
|
|
510
|
+
* preceding sibling `decorator` nodes (classes, methods) or as leading child
|
|
511
|
+
* decorators (some grammars) — collect both.
|
|
512
|
+
*/
|
|
513
|
+
function attachDecorators(sym, node) {
|
|
514
|
+
const decs = [];
|
|
515
|
+
// leading child decorators
|
|
516
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
517
|
+
const c = node.namedChild(i);
|
|
518
|
+
if (c && c.type === "decorator")
|
|
519
|
+
decs.push(decoratorText(c));
|
|
520
|
+
else if (c && c.type !== "decorator")
|
|
521
|
+
break;
|
|
522
|
+
}
|
|
523
|
+
// preceding sibling decorators (most common for classes/methods)
|
|
524
|
+
let prev = node.previousNamedSibling;
|
|
525
|
+
const lead = [];
|
|
526
|
+
while (prev && prev.type === "decorator") {
|
|
527
|
+
lead.unshift(decoratorText(prev));
|
|
528
|
+
prev = prev.previousNamedSibling;
|
|
529
|
+
}
|
|
530
|
+
const all = [...lead, ...decs].filter((t) => t.length > 0);
|
|
531
|
+
if (all.length > 0)
|
|
532
|
+
sym.decorators = all;
|
|
533
|
+
}
|
package/package.json
CHANGED