Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,151 @@ use crate::config::Settings;
parser.delete();
});

it("handles `use ... as` rename clauses", () => {
const { tree, parser, root } = parse(`
use foo::Bar as Baz;
`);
const result = extractor.extractStructure(root);

expect(result.imports).toHaveLength(1);
expect(result.imports[0].source).toBe("foo");
expect(result.imports[0].specifiers).toEqual(["Baz"]);
expect(result.imports[0].lineNumber).toBe(2);

tree.delete();
parser.delete();
});

it("captures renamed specifiers inside use-lists", () => {
const { tree, parser, root } = parse(`
use std::io::{Read as R, Write};
`);
const result = extractor.extractStructure(root);

expect(result.imports).toHaveLength(1);
expect(result.imports[0].source).toBe("std::io");
expect(result.imports[0].specifiers).toEqual(["R", "Write"]);

tree.delete();
parser.delete();
});

it("keeps source path for single-segment wildcard imports", () => {
{
const { tree, parser, root } = parse(`
use crate::*;
`);
const result = extractor.extractStructure(root);

expect(result.imports).toHaveLength(1);
expect(result.imports[0].source).toBe("crate");
expect(result.imports[0].specifiers).toEqual(["*"]);

tree.delete();
parser.delete();
}

{
const { tree, parser, root } = parse(`
use foo::*;
`);
const result = extractor.extractStructure(root);

expect(result.imports).toHaveLength(1);
expect(result.imports[0].source).toBe("foo");
expect(result.imports[0].specifiers).toEqual(["*"]);

tree.delete();
parser.delete();
}
});

it("expands nested grouped use-lists", () => {
const { tree, parser, root } = parse(`
use a::{b::{C, D}};
`);
const result = extractor.extractStructure(root);

expect(result.imports).toHaveLength(1);
expect(result.imports[0].source).toBe("a");
expect(result.imports[0].specifiers).toEqual(["C", "D"]);

tree.delete();
parser.delete();
});

it("expands nested grouped use-lists mixed with flat members", () => {
const { tree, parser, root } = parse(`
use std::{io::{Read, Write}, fmt};
`);
const result = extractor.extractStructure(root);

expect(result.imports).toHaveLength(1);
expect(result.imports[0].source).toBe("std");
expect(result.imports[0].specifiers).toEqual(["Read", "Write", "fmt"]);

tree.delete();
parser.delete();
});

it("pins behavior for bare-prefix renames (`use self::Foo as F;`)", () => {
const { tree, parser, root } = parse(`
use self::Foo as F;
`);
const result = extractor.extractStructure(root);

expect(result.imports).toHaveLength(1);
// self::Foo parses as scoped_identifier(path: self, name: Foo);
// source is the `self` prefix, the binding is the alias `F`.
expect(result.imports[0].source).toBe("self");
expect(result.imports[0].specifiers).toEqual(["F"]);

tree.delete();
parser.delete();
});

it("pins behavior for crate-prefixed renames (`use crate::Foo as F;`)", () => {
const { tree, parser, root } = parse(`
use crate::Foo as F;
`);
const result = extractor.extractStructure(root);

expect(result.imports).toHaveLength(1);
expect(result.imports[0].source).toBe("crate");
expect(result.imports[0].specifiers).toEqual(["F"]);

tree.delete();
parser.delete();
});

it("records `extern crate` declarations as imports", () => {
const { tree, parser, root } = parse(`
extern crate serde;
`);
const result = extractor.extractStructure(root);

expect(result.imports).toHaveLength(1);
expect(result.imports[0].source).toBe("serde");
expect(result.imports[0].specifiers).toEqual(["serde"]);

tree.delete();
parser.delete();
});

it("uses the alias as the specifier for `extern crate ... as`", () => {
const { tree, parser, root } = parse(`
extern crate serde as s;
`);
const result = extractor.extractStructure(root);

expect(result.imports).toHaveLength(1);
expect(result.imports[0].source).toBe("serde");
expect(result.imports[0].specifiers).toEqual(["s"]);

tree.delete();
parser.delete();
});

it("reports correct import line numbers", () => {
const { tree, parser, root } = parse(`
use std::collections::HashMap;
Expand Down Expand Up @@ -458,6 +603,58 @@ impl Config {
parser.delete();
});

it("surfaces `pub use` renamed re-exports in exports", () => {
const { tree, parser, root } = parse(`
pub use foo::Bar as Baz;
use internal::Hidden;
`);
const result = extractor.extractStructure(root);

// Still recorded as an import...
expect(result.imports.map((i) => i.specifiers)).toContainEqual(["Baz"]);

// ...and the public re-export is also surfaced as an export.
const exportNames = result.exports.map((e) => e.name);
expect(exportNames).toContain("Baz");
// Private `use` must not leak into exports.
expect(exportNames).not.toContain("Hidden");

tree.delete();
parser.delete();
});

it("surfaces `pub use` list and scoped re-exports in exports", () => {
const { tree, parser, root } = parse(`
pub use std::io::{Read, Write};
pub use crate::config::Settings;
`);
const result = extractor.extractStructure(root);

const exportNames = result.exports.map((e) => e.name);
expect(exportNames).toContain("Read");
expect(exportNames).toContain("Write");
expect(exportNames).toContain("Settings");

tree.delete();
parser.delete();
});

it("does not surface glob re-exports or `self` as named exports", () => {
const { tree, parser, root } = parse(`
pub use foo::*;
pub use std::io::{self, Read};
`);
const result = extractor.extractStructure(root);

const exportNames = result.exports.map((e) => e.name);
expect(exportNames).not.toContain("*");
expect(exportNames).not.toContain("self");
expect(exportNames).toContain("Read");

tree.delete();
parser.delete();
});

it("reports correct export line numbers", () => {
const { tree, parser, root } = parse(`
pub struct Config {
Expand Down
Loading
Loading