abbox 0.1.0 → 0.2.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 CHANGED
@@ -13,15 +13,15 @@ npx abbox compile
13
13
 
14
14
  This writes `abbox.json` in that directory. Open https://viewer.abbox.com and upload the generated file.
15
15
 
16
- `abbox.json` is a framework-independent description of the product. It is not specific to the Viewer, the CLI, or TanStack. Today it contains `schemaVersion` and screens found in static TanStack file routes. Navigation is not in the file yet. Commit `abbox.json` in the application repository.
16
+ `abbox.json` is a framework-independent description of the product. It is not specific to the Viewer, the CLI, or TanStack. It contains `schemaVersion`, screens found in static TanStack file routes, and `navigation` edges between discovered screens from static TanStack `<Link>` elements. Commit `abbox.json` in the application repository.
17
17
 
18
18
  See [docs/IR.md](docs/IR.md).
19
19
 
20
20
  ## This release
21
21
 
22
- `abbox@0.1.0` writes screens found in static TanStack `createFileRoute("...")` calls. A route literal is kept as written, including a dynamic segment such as `/projects/$projectId`.
22
+ `abbox@0.1.0` writes screens found in static TanStack `createFileRoute("...")` calls. A route literal is kept as written, including a dynamic segment such as `/projects/$projectId`. This repository also implements Link navigation in Product IR; that output appears after you compile with a build that includes navigation extraction.
23
23
 
24
- Not extracted yet: navigation (`<Link>`, `navigate()`, `redirect()`), actions, forms, APIs, auth, and other frameworks.
24
+ Not extracted yet: `navigate()`, `redirect()`, actions, forms, APIs, auth, and other frameworks.
25
25
 
26
26
  Framework-specific interpretation stays in compiler code. The Product IR does not expose framework or parser types.
27
27
 
@@ -1,7 +1,8 @@
1
1
  import { statSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  import { collectFileRouteScreens } from "../frameworks/tanstack/file-routes.js";
4
- import { productIrSchemaVersion } from "../ir/product-ir.js";
4
+ import { collectStaticLinks } from "../frameworks/tanstack/links.js";
5
+ import { productIrSchemaVersion, } from "../ir/product-ir.js";
5
6
  import { discoverSourceFiles, toProjectRelativePath, } from "./discover-source-files.js";
6
7
  export function compile(projectPath) {
7
8
  let stats;
@@ -18,15 +19,61 @@ export function compile(projectPath) {
18
19
  throw new Error(`Project path is not a directory: ${projectPath}`);
19
20
  }
20
21
  const projectRoot = path.resolve(projectPath);
21
- const hits = collectFileRouteScreens(discoverSourceFiles(projectRoot));
22
+ const files = discoverSourceFiles(projectRoot);
23
+ const routeHits = collectFileRouteScreens(files);
24
+ const linkHits = collectStaticLinks(files);
25
+ const screensByFile = groupRoutesByFile(routeHits);
26
+ const knownRoutes = new Set(routeHits.map((hit) => hit.route));
27
+ const navigation = buildNavigation(screensByFile, linkHits, knownRoutes);
22
28
  return {
23
29
  schemaVersion: productIrSchemaVersion,
24
- screens: hits.map((hit) => ({
30
+ screens: routeHits.map((hit) => ({
25
31
  route: hit.route,
26
32
  source: { file: toProjectRelativePath(projectRoot, hit.filePath) },
27
33
  })),
34
+ navigation,
28
35
  };
29
36
  }
37
+ function groupRoutesByFile(hits) {
38
+ const byFile = new Map();
39
+ for (const hit of hits) {
40
+ const routes = byFile.get(hit.filePath);
41
+ if (routes === undefined) {
42
+ byFile.set(hit.filePath, [hit.route]);
43
+ }
44
+ else {
45
+ routes.push(hit.route);
46
+ }
47
+ }
48
+ return byFile;
49
+ }
50
+ function buildNavigation(screensByFile, linkHits, knownRoutes) {
51
+ const edges = new Map();
52
+ for (const link of linkHits) {
53
+ if (!knownRoutes.has(link.to)) {
54
+ continue;
55
+ }
56
+ const routesInFile = screensByFile.get(link.filePath);
57
+ if (routesInFile === undefined || routesInFile.length !== 1) {
58
+ continue;
59
+ }
60
+ const from = routesInFile[0];
61
+ if (from === undefined) {
62
+ continue;
63
+ }
64
+ const key = `${from}\0${link.to}`;
65
+ if (!edges.has(key)) {
66
+ edges.set(key, { from, to: link.to });
67
+ }
68
+ }
69
+ return [...edges.values()].sort((left, right) => {
70
+ const byFrom = left.from.localeCompare(right.from);
71
+ if (byFrom !== 0) {
72
+ return byFrom;
73
+ }
74
+ return left.to.localeCompare(right.to);
75
+ });
76
+ }
30
77
  function isNotFound(error) {
31
78
  return (typeof error === "object" &&
32
79
  error !== null &&
@@ -1 +1 @@
1
- {"version":3,"file":"compile.js","sourceRoot":"","sources":["../../src/compiler/compile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,uBAAuB,EAAE,MAAM,uCAAuC,CAAC;AAChF,OAAO,EAAkB,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7E,OAAO,EACL,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,4BAA4B,CAAC;AAEpC,MAAM,UAAU,OAAO,CAAC,WAAmB;IACzC,IAAI,KAAkC,CAAC;IACvC,IAAI,CAAC;QACH,KAAK,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC;IAChC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,gCAAgC,WAAW,EAAE,CAAC,CAAC;QACjE,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;IAED,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,oCAAoC,WAAW,EAAE,CAAC,CAAC;IACrE,CAAC;IAED,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IAC9C,MAAM,IAAI,GAAG,uBAAuB,CAAC,mBAAmB,CAAC,WAAW,CAAC,CAAC,CAAC;IACvE,OAAO;QACL,aAAa,EAAE,sBAAsB;QACrC,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YAC1B,KAAK,EAAE,GAAG,CAAC,KAAK;YAChB,MAAM,EAAE,EAAE,IAAI,EAAE,qBAAqB,CAAC,WAAW,EAAE,GAAG,CAAC,QAAQ,CAAC,EAAE;SACnE,CAAC,CAAC;KACJ,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,KAAc;IAChC,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,MAAM,IAAI,KAAK;QACf,KAAK,CAAC,IAAI,KAAK,QAAQ,CACxB,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"compile.js","sourceRoot":"","sources":["../../src/compiler/compile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,uBAAuB,EAAE,MAAM,uCAAuC,CAAC;AAChF,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EAGL,sBAAsB,GACvB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,4BAA4B,CAAC;AAEpC,MAAM,UAAU,OAAO,CAAC,WAAmB;IACzC,IAAI,KAAkC,CAAC;IACvC,IAAI,CAAC;QACH,KAAK,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC;IAChC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,gCAAgC,WAAW,EAAE,CAAC,CAAC;QACjE,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;IAED,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,oCAAoC,WAAW,EAAE,CAAC,CAAC;IACrE,CAAC;IAED,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IAC9C,MAAM,KAAK,GAAG,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAC/C,MAAM,SAAS,GAAG,uBAAuB,CAAC,KAAK,CAAC,CAAC;IACjD,MAAM,QAAQ,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;IAE3C,MAAM,aAAa,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;IACnD,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;IAE/D,MAAM,UAAU,GAAG,eAAe,CAAC,aAAa,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC;IAEzE,OAAO;QACL,aAAa,EAAE,sBAAsB;QACrC,OAAO,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YAC/B,KAAK,EAAE,GAAG,CAAC,KAAK;YAChB,MAAM,EAAE,EAAE,IAAI,EAAE,qBAAqB,CAAC,WAAW,EAAE,GAAG,CAAC,QAAQ,CAAC,EAAE;SACnE,CAAC,CAAC;QACH,UAAU;KACX,CAAC;AACJ,CAAC;AAED,SAAS,iBAAiB,CACxB,IAAoD;IAEpD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAoB,CAAC;IAC3C,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACxC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;QACxC,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,eAAe,CACtB,aAAoC,EACpC,QAAqD,EACrD,WAAgC;IAEhC,MAAM,KAAK,GAAG,IAAI,GAAG,EAAsB,CAAC;IAE5C,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;QAC5B,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;YAC9B,SAAS;QACX,CAAC;QAED,MAAM,YAAY,GAAG,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACtD,IAAI,YAAY,KAAK,SAAS,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC5D,SAAS;QACX,CAAC;QAED,MAAM,IAAI,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC;QAC7B,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,SAAS;QACX,CAAC;QAED,MAAM,GAAG,GAAG,GAAG,IAAI,KAAK,IAAI,CAAC,EAAE,EAAE,CAAC;QAClC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACpB,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;QACxC,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QAC9C,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACnD,IAAI,MAAM,KAAK,CAAC,EAAE,CAAC;YACjB,OAAO,MAAM,CAAC;QAChB,CAAC;QACD,OAAO,IAAI,CAAC,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,UAAU,CAAC,KAAc;IAChC,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,MAAM,IAAI,KAAK;QACf,KAAK,CAAC,IAAI,KAAK,QAAQ,CACxB,CAAC;AACJ,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import { Node, Project, SyntaxKind } from "ts-morph";
2
- const tanstackRouterModule = "@tanstack/react-router";
2
+ import { importedLocalNames } from "./named-import.js";
3
3
  const createFileRouteName = "createFileRoute";
4
4
  export function collectFileRouteScreens(files) {
5
5
  if (files.length === 0) {
@@ -14,24 +14,8 @@ export function collectFileRouteScreens(files) {
14
14
  }
15
15
  return hits;
16
16
  }
17
- function importedLocalNames(sourceFile) {
18
- const names = new Set();
19
- for (const declaration of sourceFile.getImportDeclarations()) {
20
- if (declaration.isTypeOnly() ||
21
- declaration.getModuleSpecifierValue() !== tanstackRouterModule) {
22
- continue;
23
- }
24
- for (const named of declaration.getNamedImports()) {
25
- if (named.isTypeOnly() || named.getName() !== createFileRouteName) {
26
- continue;
27
- }
28
- names.add(named.getAliasNode()?.getText() ?? named.getName());
29
- }
30
- }
31
- return names;
32
- }
33
17
  function screensInFile(sourceFile) {
34
- const names = importedLocalNames(sourceFile);
18
+ const names = importedLocalNames(sourceFile, createFileRouteName);
35
19
  if (names.size === 0) {
36
20
  return [];
37
21
  }
@@ -1 +1 @@
1
- {"version":3,"file":"file-routes.js","sourceRoot":"","sources":["../../../src/frameworks/tanstack/file-routes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,OAAO,EAAmB,UAAU,EAAE,MAAM,UAAU,CAAC;AAEtE,MAAM,oBAAoB,GAAG,wBAAwB,CAAC;AACtD,MAAM,mBAAmB,GAAG,iBAAiB,CAAC;AAO9C,MAAM,UAAU,uBAAuB,CACrC,KAAwB;IAExB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC;QAC1B,2BAA2B,EAAE,IAAI;KAClC,CAAC,CAAC;IACH,MAAM,IAAI,GAAmB,EAAE,CAAC;IAChC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,IAAI,CAAC,GAAG,aAAa,CAAC,OAAO,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACjE,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,kBAAkB,CAAC,UAAsB;IAChD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,KAAK,MAAM,WAAW,IAAI,UAAU,CAAC,qBAAqB,EAAE,EAAE,CAAC;QAC7D,IACE,WAAW,CAAC,UAAU,EAAE;YACxB,WAAW,CAAC,uBAAuB,EAAE,KAAK,oBAAoB,EAC9D,CAAC;YACD,SAAS;QACX,CAAC;QAED,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,eAAe,EAAE,EAAE,CAAC;YAClD,IAAI,KAAK,CAAC,UAAU,EAAE,IAAI,KAAK,CAAC,OAAO,EAAE,KAAK,mBAAmB,EAAE,CAAC;gBAClE,SAAS;YACX,CAAC;YACD,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,YAAY,EAAE,EAAE,OAAO,EAAE,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QAChE,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,aAAa,CAAC,UAAsB;IAC3C,MAAM,KAAK,GAAG,kBAAkB,CAAC,UAAU,CAAC,CAAC;IAC7C,IAAI,KAAK,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,IAAI,GAAmB,EAAE,CAAC;IAChC,KAAK,MAAM,IAAI,IAAI,UAAU,CAAC,oBAAoB,CAChD,UAAU,CAAC,cAAc,CAC1B,EAAE,CAAC;QACF,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;QACpC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;YAC/D,SAAS;QACX,CAAC;QAED,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;QACjC,MAAM,QAAQ,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACzB,IACE,IAAI,CAAC,MAAM,KAAK,CAAC;YACjB,QAAQ,KAAK,SAAS;YACtB,CAAC,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,EAC/B,CAAC;YACD,SAAS;QACX,CAAC;QAED,IAAI,CAAC,IAAI,CAAC;YACR,KAAK,EAAE,QAAQ,CAAC,cAAc,EAAE;YAChC,QAAQ,EAAE,UAAU,CAAC,WAAW,EAAE;SACnC,CAAC,CAAC;IACL,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
1
+ {"version":3,"file":"file-routes.js","sourceRoot":"","sources":["../../../src/frameworks/tanstack/file-routes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,OAAO,EAAmB,UAAU,EAAE,MAAM,UAAU,CAAC;AACtE,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAEvD,MAAM,mBAAmB,GAAG,iBAAiB,CAAC;AAO9C,MAAM,UAAU,uBAAuB,CACrC,KAAwB;IAExB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC;QAC1B,2BAA2B,EAAE,IAAI;KAClC,CAAC,CAAC;IACH,MAAM,IAAI,GAAmB,EAAE,CAAC;IAChC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,IAAI,CAAC,GAAG,aAAa,CAAC,OAAO,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACjE,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,aAAa,CAAC,UAAsB;IAC3C,MAAM,KAAK,GAAG,kBAAkB,CAAC,UAAU,EAAE,mBAAmB,CAAC,CAAC;IAClE,IAAI,KAAK,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,IAAI,GAAmB,EAAE,CAAC;IAChC,KAAK,MAAM,IAAI,IAAI,UAAU,CAAC,oBAAoB,CAChD,UAAU,CAAC,cAAc,CAC1B,EAAE,CAAC;QACF,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;QACpC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;YAC/D,SAAS;QACX,CAAC;QAED,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;QACjC,MAAM,QAAQ,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACzB,IACE,IAAI,CAAC,MAAM,KAAK,CAAC;YACjB,QAAQ,KAAK,SAAS;YACtB,CAAC,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,EAC/B,CAAC;YACD,SAAS;QACX,CAAC;QAED,IAAI,CAAC,IAAI,CAAC;YACR,KAAK,EAAE,QAAQ,CAAC,cAAc,EAAE;YAChC,QAAQ,EAAE,UAAU,CAAC,WAAW,EAAE;SACnC,CAAC,CAAC;IACL,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,5 @@
1
+ export interface StaticLinkHit {
2
+ filePath: string;
3
+ to: string;
4
+ }
5
+ export declare function collectStaticLinks(files: readonly string[]): StaticLinkHit[];
@@ -0,0 +1,70 @@
1
+ import { Node, Project, SyntaxKind } from "ts-morph";
2
+ import { importedLocalNames } from "./named-import.js";
3
+ const linkExportName = "Link";
4
+ export function collectStaticLinks(files) {
5
+ if (files.length === 0) {
6
+ return [];
7
+ }
8
+ const project = new Project({
9
+ skipAddingFilesFromTsConfig: true,
10
+ });
11
+ const hits = [];
12
+ for (const file of files) {
13
+ hits.push(...linksInFile(project.addSourceFileAtPath(file)));
14
+ }
15
+ return hits;
16
+ }
17
+ function linksInFile(sourceFile) {
18
+ const names = importedLocalNames(sourceFile, linkExportName);
19
+ if (names.size === 0) {
20
+ return [];
21
+ }
22
+ const hits = [];
23
+ for (const element of sourceFile.getDescendantsOfKind(SyntaxKind.JsxSelfClosingElement)) {
24
+ const hit = linkDestinationFromElement(element.getTagNameNode(), names, element);
25
+ if (hit !== undefined) {
26
+ hits.push({ filePath: sourceFile.getFilePath(), to: hit });
27
+ }
28
+ }
29
+ for (const element of sourceFile.getDescendantsOfKind(SyntaxKind.JsxOpeningElement)) {
30
+ const hit = linkDestinationFromElement(element.getTagNameNode(), names, element);
31
+ if (hit !== undefined) {
32
+ hits.push({ filePath: sourceFile.getFilePath(), to: hit });
33
+ }
34
+ }
35
+ return hits;
36
+ }
37
+ function linkDestinationFromElement(tagName, names, element) {
38
+ if (!Node.isIdentifier(tagName) || !names.has(tagName.getText())) {
39
+ return undefined;
40
+ }
41
+ const toAttribute = element
42
+ .getAttributes()
43
+ .find((attribute) => Node.isJsxAttribute(attribute) &&
44
+ attribute.getNameNode().getText() === "to");
45
+ if (toAttribute === undefined || !Node.isJsxAttribute(toAttribute)) {
46
+ return undefined;
47
+ }
48
+ const initializer = toAttribute.getInitializer();
49
+ if (initializer === undefined) {
50
+ return undefined;
51
+ }
52
+ if (Node.isStringLiteral(initializer)) {
53
+ return absoluteRouteLiteral(initializer.getLiteralText());
54
+ }
55
+ if (Node.isJsxExpression(initializer) &&
56
+ initializer.getExpression() !== undefined) {
57
+ const expression = initializer.getExpression();
58
+ if (expression !== undefined && Node.isStringLiteral(expression)) {
59
+ return absoluteRouteLiteral(expression.getLiteralText());
60
+ }
61
+ }
62
+ return undefined;
63
+ }
64
+ function absoluteRouteLiteral(text) {
65
+ if (text.length === 0 || !text.startsWith("/") || text.startsWith("//")) {
66
+ return undefined;
67
+ }
68
+ return text;
69
+ }
70
+ //# sourceMappingURL=links.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"links.js","sourceRoot":"","sources":["../../../src/frameworks/tanstack/links.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { SourceFile } from "ts-morph";
2
+ export declare function importedLocalNames(sourceFile: SourceFile, exportName: string): Set<string>;
@@ -0,0 +1,18 @@
1
+ const tanstackRouterModule = "@tanstack/react-router";
2
+ export function importedLocalNames(sourceFile, exportName) {
3
+ const names = new Set();
4
+ for (const declaration of sourceFile.getImportDeclarations()) {
5
+ if (declaration.isTypeOnly() ||
6
+ declaration.getModuleSpecifierValue() !== tanstackRouterModule) {
7
+ continue;
8
+ }
9
+ for (const named of declaration.getNamedImports()) {
10
+ if (named.isTypeOnly() || named.getName() !== exportName) {
11
+ continue;
12
+ }
13
+ names.add(named.getAliasNode()?.getText() ?? named.getName());
14
+ }
15
+ }
16
+ return names;
17
+ }
18
+ //# sourceMappingURL=named-import.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"named-import.js","sourceRoot":"","sources":["../../../src/frameworks/tanstack/named-import.ts"],"names":[],"mappings":"AAEA,MAAM,oBAAoB,GAAG,wBAAwB,CAAC;AAEtD,MAAM,UAAU,kBAAkB,CAChC,UAAsB,EACtB,UAAkB;IAElB,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,KAAK,MAAM,WAAW,IAAI,UAAU,CAAC,qBAAqB,EAAE,EAAE,CAAC;QAC7D,IACE,WAAW,CAAC,UAAU,EAAE;YACxB,WAAW,CAAC,uBAAuB,EAAE,KAAK,oBAAoB,EAC9D,CAAC;YACD,SAAS;QACX,CAAC;QAED,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,eAAe,EAAE,EAAE,CAAC;YAClD,IAAI,KAAK,CAAC,UAAU,EAAE,IAAI,KAAK,CAAC,OAAO,EAAE,KAAK,UAAU,EAAE,CAAC;gBACzD,SAAS;YACX,CAAC;YACD,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,YAAY,EAAE,EAAE,OAAO,EAAE,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QAChE,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export { compile } from "./compiler/compile.js";
2
- export type { ProductIr, Screen, ScreenSource } from "./ir/product-ir.js";
2
+ export type { Navigation, ProductIr, Screen, ScreenSource, } from "./ir/product-ir.js";
3
3
  export { productIrSchemaVersion } from "./ir/product-ir.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAEhD,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAOhD,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC"}
@@ -6,7 +6,12 @@ export interface Screen {
6
6
  route: string;
7
7
  source: ScreenSource;
8
8
  }
9
+ export interface Navigation {
10
+ from: string;
11
+ to: string;
12
+ }
9
13
  export interface ProductIr {
10
14
  schemaVersion: typeof productIrSchemaVersion;
11
15
  screens: Screen[];
16
+ navigation: Navigation[];
12
17
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "abbox",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Compiler that produces a framework-independent Product IR from application source code.",
5
5
  "author": "Kamran Elchuzade",
6
6
  "license": "Apache-2.0",
@@ -23,7 +23,7 @@
23
23
  }
24
24
  },
25
25
  "bin": {
26
- "abbox": "./dist/cli.js"
26
+ "abbox": "dist/cli.js"
27
27
  },
28
28
  "files": [
29
29
  "dist"