weavatrix-rust 2.1.1

Protocol-independent Rust repository intelligence: typed evidence graphs for impact, architecture, APIs, Git, search, and memory
Documentation
mod language_fixture;

use language_fixture::Fixture;
use weavatrix_rust::{Analyzer, EdgeKind};

#[test]
fn resolves_the_module_aliases_a_project_declares() {
    let fixture = Fixture::new();
    fixture.write(
        "tsconfig.json",
        r#"{
  // Comments and trailing commas are normal in this file.
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@app/*": ["src/app/*"],
      "@shared": ["src/shared/index.ts"],
    },
  },
}"#,
    );
    fixture.write(
        "package.json",
        r##"{"name":"root","workspaces":["packages/*"],"imports":{"#config/*":"./src/config/*"}}"##,
    );
    fixture.write("src/app/service.ts", "export function serve() {}\n");
    fixture.write("src/shared/index.ts", "export const shared = 1;\n");
    fixture.write("src/config/db.ts", "export const url = '';\n");
    fixture.write("src/base/root.ts", "export const root = 1;\n");
    fixture.write("packages/ui/package.json", r#"{"name":"@acme/ui"}"#);
    fixture.write("packages/ui/index.ts", "export const Button = 1;\n");
    fixture.write(
        "src/entry.ts",
        "import { serve } from '@app/service';\nimport { shared } from '@shared';\nimport { url } from '#config/db';\nimport { Button } from '@acme/ui';\nimport { root } from 'src/base/root';\nimport { missing } from './nowhere';\nexport const use = [serve, shared, url, Button, root, missing];\n",
    );

    let snapshot = Analyzer::default().analyze(&fixture.root).unwrap();
    let imports = |target: &str| {
        snapshot.edges.iter().any(|edge| {
            edge.kind == EdgeKind::Imports
                && edge.source.as_str() == "file:src/entry.ts"
                && edge.target.as_str() == target
        })
    };
    for (target, form) in [
        ("file:src/app/service.ts", "tsconfig paths wildcard"),
        ("file:src/shared/index.ts", "tsconfig paths exact mapping"),
        ("file:src/config/db.ts", "package.json subpath import"),
        ("file:packages/ui/index.ts", "workspace package name"),
        ("file:src/base/root.ts", "tsconfig baseUrl"),
    ] {
        assert!(imports(target), "{form} must resolve to {target}");
    }
    assert!(
        !snapshot
            .nodes
            .iter()
            .any(|node| node.id.as_str().starts_with("package:") && node.label.starts_with('@')),
        "an aliased local import must not be recorded as an external package"
    );
    let unresolved = snapshot
        .diagnostics
        .iter()
        .filter(|diagnostic| diagnostic.code == "import.unresolved")
        .collect::<Vec<_>>();
    assert_eq!(
        unresolved.len(),
        1,
        "the one genuinely missing target is reported, got {unresolved:?}"
    );
    assert!(
        unresolved[0].message.contains("./nowhere"),
        "the diagnostic names the specifier, got {:?}",
        unresolved[0].message
    );
}

#[test]
fn resolves_javascript_modules_whose_stems_contain_dots() {
    let fixture = Fixture::new();
    fixture.write(
        "jsconfig.json",
        r##"{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {"#tests/*": ["tests/*"]}
  }
}"##,
    );
    fixture.write(
        "tests/common/common.actions.js",
        "export const action = true;\n",
    );
    fixture.write(
        "src/data/attacks.mock.data.js",
        "export const attacks = [];\n",
    );
    fixture.write("src/exact.module.js", "export const exact = true;\n");
    fixture.write(
        "src/entry.js",
        "import { action } from '#tests/common/common.actions';\n\
         import { attacks } from './data/attacks.mock.data';\n\
         import { exact } from './exact.module.js';\n\
         export const values = [action, attacks, exact];\n",
    );

    let snapshot = Analyzer::default().analyze(&fixture.root).unwrap();
    let targets = snapshot
        .edges
        .iter()
        .filter(|edge| {
            edge.kind == EdgeKind::Imports && edge.source.as_str() == "file:src/entry.js"
        })
        .map(|edge| edge.target.as_str())
        .collect::<Vec<_>>();

    for target in [
        "file:tests/common/common.actions.js",
        "file:src/data/attacks.mock.data.js",
        "file:src/exact.module.js",
    ] {
        assert!(
            targets.contains(&target),
            "dotted module stem must resolve to {target}, got {targets:?}"
        );
    }
    assert!(
        snapshot
            .diagnostics
            .iter()
            .all(|diagnostic| diagnostic.code != "import.unresolved"),
        "all dotted module imports should resolve, got {:?}",
        snapshot.diagnostics
    );
}

#[test]
fn resolves_typescript_runtime_extensions_without_overriding_real_javascript() {
    let fixture = Fixture::new();
    fixture.write(
        "src/entry.ts",
        "import './plain.js';\nimport './component.jsx';\nimport './module.js';\nimport './common.jsx';\nimport './exact.js';\n",
    );
    fixture.write("src/plain.ts", "export const plain = true;\n");
    fixture.write("src/component.tsx", "export const component = true;\n");
    fixture.write("src/module.mts", "export const moduleValue = true;\n");
    fixture.write("src/common.cts", "export const common = true;\n");
    fixture.write("src/exact.js", "export const runtime = true;\n");
    fixture.write("src/exact.ts", "export const source = true;\n");

    let snapshot = Analyzer::default().analyze(&fixture.root).unwrap();
    let imports = snapshot
        .edges
        .iter()
        .filter(|edge| {
            edge.kind == EdgeKind::Imports && edge.source.as_str() == "file:src/entry.ts"
        })
        .map(|edge| edge.target.as_str())
        .collect::<Vec<_>>();

    for target in [
        "file:src/plain.ts",
        "file:src/component.tsx",
        "file:src/module.mts",
        "file:src/common.cts",
        "file:src/exact.js",
    ] {
        assert!(
            imports.contains(&target),
            "runtime specifier must resolve to {target}, got {imports:?}"
        );
    }
    assert!(
        !imports.contains(&"file:src/exact.ts"),
        "an exact JavaScript target must win over its TypeScript sibling"
    );
    assert!(
        snapshot
            .diagnostics
            .iter()
            .all(|diagnostic| diagnostic.code != "import.unresolved"),
        "all runtime specifiers should resolve, got {:?}",
        snapshot.diagnostics
    );
}

#[test]
fn resolves_imports_through_re_export_barrels() {
    let fixture = Fixture::new();
    fixture.write("src/shared/Button.tsx", "export function Button() {}\n");
    fixture.write("src/shared/Input.tsx", "export function Input() {}\n");
    fixture.write(
        "src/shared/index.ts",
        "export { Button } from './Button';\nexport * from './Input';\n",
    );
    fixture.write(
        "src/app/App.tsx",
        "import { Button, Input } from '../shared';\nexport function App() { return Button(); }\n",
    );
    let snapshot = Analyzer::default().analyze(&fixture.root).unwrap();
    let edge = |kind: EdgeKind, source: &str, target: &str| {
        snapshot.edges.iter().any(|item| {
            item.kind == kind && item.source.as_str() == source && item.target.as_str() == target
        })
    };
    assert!(
        edge(
            EdgeKind::ReExports,
            "file:src/shared/index.ts",
            "file:src/shared/Button.tsx"
        ),
        "named re-export is recorded as re-export evidence"
    );
    assert!(
        edge(
            EdgeKind::ReExports,
            "file:src/shared/index.ts",
            "file:src/shared/Input.tsx"
        ),
        "star re-export is recorded as re-export evidence"
    );
    assert!(
        edge(
            EdgeKind::Imports,
            "file:src/app/App.tsx",
            "file:src/shared/index.ts"
        ),
        "the barrel itself stays an import target"
    );
    for defining in ["file:src/shared/Button.tsx", "file:src/shared/Input.tsx"] {
        assert!(
            edge(EdgeKind::Imports, "file:src/app/App.tsx", defining),
            "barrel import must reach {defining} through the re-export chain"
        );
    }
}