Skip to content
Merged
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
41 changes: 41 additions & 0 deletions crates/codegraph-core/src/extractors/javascript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3161,6 +3161,7 @@ fn handle_export_declaration(decl: &Node, source: &[u8], symbols: &mut FileSymbo
"class_declaration" | "abstract_class_declaration" => ("class", "name"),
"interface_declaration" => ("interface", "name"),
"type_alias_declaration" => ("type", "name"),
"enum_declaration" => ("enum", "name"),
"lexical_declaration" | "variable_declaration" => {
collect_exported_var_declarations(decl, source, symbols);
return;
Expand Down Expand Up @@ -10741,6 +10742,46 @@ mod tests {
);
}

#[test]
fn exports_an_enum_declaration_with_kind_enum() {
// Regression guard for #2560: enum_declaration had no arm in
// handle_export_declaration's match, so `export enum Foo {}` was
// extracted as a Definition (via handle_enum_decl) but never marked
// exported.
let s = parse_ts("export enum Color { Red, Green, Blue }");
assert!(
s.definitions
.iter()
.any(|d| d.name == "Color" && d.kind == "enum" && d.line == 1),
"expected 'Color' defined as enum at line 1; got: {:?}",
s.definitions
);
assert!(
s.exports
.iter()
.any(|e| e.name == "Color" && e.kind == "enum" && e.line == 1),
"expected 'Color' exported as enum at line 1; got: {:?}",
s.exports
);
}

#[test]
fn does_not_export_a_non_exported_enum() {
let s = parse_ts("enum Internal { A, B }");
assert!(
s.definitions
.iter()
.any(|d| d.name == "Internal" && d.kind == "enum"),
"expected 'Internal' defined as enum; got: {:?}",
s.definitions
);
assert!(
!s.exports.iter().any(|e| e.name == "Internal"),
"did not expect 'Internal' to be exported; got: {:?}",
s.exports
);
}

#[test]
fn skips_a_comment_between_the_export_keyword_and_the_declaration() {
let s = parse_js("export\n// why is this exported\nconst withComment = 1;");
Expand Down
5 changes: 3 additions & 2 deletions src/extractors/javascript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ const EXPORT_DECL_KIND: Record<string, string> = {
abstract_class_declaration: 'class',
interface_declaration: 'interface',
type_alias_declaration: 'type',
enum_declaration: 'enum',
};

/**
Expand All @@ -218,8 +219,8 @@ const EXPORT_DECL_KIND: Record<string, string> = {
* walk-based `handleExportStmt`) so they can't drift apart on what counts as
* an export — see the "two code paths" gotcha for this extractor.
*
* Named function/class/interface/type declarations carry their own `name`
* field. `export const/let/var …` has no such field — each declarator's value
* Named function/class/interface/type/enum declarations carry their own
* `name` field. `export const/let/var …` has no such field — each declarator's value
* is classified the same way `handleVariableDeclarator` classifies it when
* building the matching Definition (function-valued → kind 'function'; any
* other `const` initializer shape → kind 'constant', regardless of complexity —
Expand Down
31 changes: 31 additions & 0 deletions tests/parsers/javascript.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3973,6 +3973,37 @@ function runDemo(reporter: Reporter, users: string[]): void {
});
});

describe('export enum declarations produce an Export record (#2560)', () => {
// Regression guard for #2560: `enum_declaration` was extracted as a
// Definition (via handleEnumDecl) but had no entry in EXPORT_DECL_KIND,
// so collectExportedDeclarations silently no-op'd for it — the enum
// itself was never marked exported, even though real TS/JS semantics say
// `export enum Foo {}` genuinely exports `Foo`.
function parseTS(code) {
const parser = parsers.get('typescript');
const tree = parser.parse(code);
return extractSymbols(tree, 'test.ts');
}

it('lists an exported enum with kind "enum"', () => {
const symbols = parseTS(`export enum Color { Red, Green, Blue }`);
expect(symbols.definitions).toContainEqual(
expect.objectContaining({ name: 'Color', kind: 'enum', line: 1 }),
);
expect(symbols.exports).toContainEqual(
expect.objectContaining({ name: 'Color', kind: 'enum', line: 1 }),
);
});

it('does not list a non-exported enum', () => {
const symbols = parseTS(`enum Internal { A, B }`);
expect(symbols.definitions).toContainEqual(
expect.objectContaining({ name: 'Internal', kind: 'enum' }),
);
expect(symbols.exports.some((e) => e.name === 'Internal')).toBe(false);
});
});

describe('export line matches the declaration, not the `export` keyword (#2293)', () => {
// Regression guard for #2293: collectExportedDeclarations computed a single
// `exportLine` from the wrapping `export_statement` node and applied it to
Expand Down
Loading