use fallow_types::extract::ExportName;
use crate::tests::parse_ts as parse_source;
#[test]
fn jsdoc_public_tag_on_named_export() {
let info = parse_source("/** @public */\nexport const foo = 1;");
assert_eq!(info.exports.len(), 1);
assert!(info.exports[0].is_public);
}
#[test]
fn jsdoc_public_tag_on_function_export() {
let info = parse_source("/** @public */\nexport function bar() {}");
assert_eq!(info.exports.len(), 1);
assert!(info.exports[0].is_public);
}
#[test]
fn jsdoc_public_tag_on_default_export() {
let info = parse_source("/** @public */\nexport default function main() {}");
assert_eq!(info.exports.len(), 1);
assert!(info.exports[0].is_public);
}
#[test]
fn jsdoc_public_tag_on_class_export() {
let info = parse_source("/** @public */\nexport class Foo {}");
assert_eq!(info.exports.len(), 1);
assert!(info.exports[0].is_public);
}
#[test]
fn jsdoc_public_tag_on_type_export() {
let info = parse_source("/** @public */\nexport type Foo = string;");
assert_eq!(info.exports.len(), 1);
assert!(info.exports[0].is_public);
}
#[test]
fn jsdoc_public_tag_on_interface_export() {
let info = parse_source("/** @public */\nexport interface Bar {}");
assert_eq!(info.exports.len(), 1);
assert!(info.exports[0].is_public);
}
#[test]
fn jsdoc_public_tag_on_enum_export() {
let info = parse_source("/** @public */\nexport enum Status { Active, Inactive }");
assert_eq!(info.exports.len(), 1);
assert!(info.exports[0].is_public);
}
#[test]
fn jsdoc_public_tag_multiline() {
let info = parse_source("/**\n * Some description.\n * @public\n */\nexport const foo = 1;");
assert_eq!(info.exports.len(), 1);
assert!(info.exports[0].is_public);
}
#[test]
fn jsdoc_public_tag_with_other_tags() {
let info = parse_source("/** @deprecated @public */\nexport const foo = 1;");
assert_eq!(info.exports.len(), 1);
assert!(info.exports[0].is_public);
}
#[test]
fn jsdoc_api_public_tag() {
let info = parse_source("/** @api public */\nexport const foo = 1;");
assert_eq!(info.exports.len(), 1);
assert!(info.exports[0].is_public);
}
#[test]
fn no_jsdoc_tag_not_public() {
let info = parse_source("export const foo = 1;");
assert_eq!(info.exports.len(), 1);
assert!(!info.exports[0].is_public);
}
#[test]
fn line_comment_not_jsdoc() {
let info = parse_source("// @public\nexport const foo = 1;");
assert_eq!(info.exports.len(), 1);
assert!(!info.exports[0].is_public);
}
#[test]
fn jsdoc_public_does_not_match_public_foo() {
let info = parse_source("/** @publicFoo */\nexport const foo = 1;");
assert_eq!(info.exports.len(), 1);
assert!(!info.exports[0].is_public);
}
#[test]
fn jsdoc_public_does_not_match_public_underscore() {
let info = parse_source("/** @public_api */\nexport const foo = 1;");
assert_eq!(info.exports.len(), 1);
assert!(!info.exports[0].is_public);
}
#[test]
fn jsdoc_apipublic_no_space_does_not_match() {
let info = parse_source("/** @apipublic */\nexport const foo = 1;");
assert_eq!(info.exports.len(), 1);
assert!(!info.exports[0].is_public);
}
#[test]
fn jsdoc_public_on_export_specifier_list() {
let source = "const foo = 1;\nconst bar = 2;\n/** @public */\nexport { foo, bar };";
let info = parse_source(source);
assert_eq!(info.exports.len(), 2);
assert!(info.exports[0].is_public);
assert!(info.exports[1].is_public);
}
#[test]
fn jsdoc_public_only_applies_to_attached_export() {
let source = "/** @public */\nexport const foo = 1;\nexport const bar = 2;";
let info = parse_source(source);
assert_eq!(info.exports.len(), 2);
assert!(info.exports[0].is_public);
assert!(!info.exports[1].is_public);
}
#[test]
fn jsdoc_public_block_comment_not_jsdoc() {
let info = parse_source("/* @public */\nexport const foo = 1;");
assert_eq!(info.exports.len(), 1);
assert!(!info.exports[0].is_public);
}
#[test]
fn jsdoc_public_on_anonymous_default_export() {
let info = parse_source("/** @public */\nexport default function() {}");
assert_eq!(info.exports.len(), 1);
assert_eq!(info.exports[0].name, ExportName::Default);
assert!(info.exports[0].is_public);
}
#[test]
fn jsdoc_public_on_arrow_default_export() {
let info = parse_source("/** @public */\nexport default () => {};");
assert_eq!(info.exports.len(), 1);
assert_eq!(info.exports[0].name, ExportName::Default);
assert!(info.exports[0].is_public);
}
#[test]
fn jsdoc_public_on_default_expression_export() {
let info = parse_source("/** @public */\nexport default 42;");
assert_eq!(info.exports.len(), 1);
assert_eq!(info.exports[0].name, ExportName::Default);
assert!(info.exports[0].is_public);
}
#[test]
fn jsdoc_public_on_let_export() {
let info = parse_source("/** @public */\nexport let count = 0;");
assert_eq!(info.exports.len(), 1);
assert_eq!(info.exports[0].name, ExportName::Named("count".to_string()));
assert!(info.exports[0].is_public);
}
#[test]
fn jsdoc_public_with_trailing_description() {
let info = parse_source("/** @public This is always exported */\nexport const foo = 1;");
assert_eq!(info.exports.len(), 1);
assert!(info.exports[0].is_public);
}
#[test]
fn jsdoc_api_public_with_extra_whitespace() {
let info = parse_source("/** @api public */\nexport const foo = 1;");
assert_eq!(info.exports.len(), 1);
assert!(info.exports[0].is_public);
}
#[test]
fn jsdoc_api_public_with_newline() {
let info = parse_source("/**\n * @api\n * public\n */\nexport const foo = 1;");
assert_eq!(info.exports.len(), 1);
assert!(!info.exports[0].is_public);
}
#[test]
fn jsdoc_api_publicfoo_does_not_match() {
let info = parse_source("/** @api publicFoo */\nexport const foo = 1;");
assert_eq!(info.exports.len(), 1);
assert!(!info.exports[0].is_public);
}
#[test]
fn jsdoc_public_multiple_exports_all_tagged() {
let source = "/** @public */\nexport const a = 1;\n/** @public */\nexport const b = 2;";
let info = parse_source(source);
assert_eq!(info.exports.len(), 2);
assert!(info.exports[0].is_public);
assert!(info.exports[1].is_public);
}
#[test]
fn jsdoc_public_mixed_three_exports() {
let source = "/** @public */\nexport const a = 1;\nexport const b = 2;\n/** @public */\nexport const c = 3;";
let info = parse_source(source);
assert_eq!(info.exports.len(), 3);
assert!(info.exports[0].is_public);
assert!(!info.exports[1].is_public);
assert!(info.exports[2].is_public);
}
#[test]
fn jsdoc_public_does_not_match_numeric_suffix() {
let info = parse_source("/** @public2 */\nexport const foo = 1;");
assert_eq!(info.exports.len(), 1);
assert!(!info.exports[0].is_public);
}
#[test]
fn jsdoc_public_on_async_function_export() {
let info = parse_source("/** @public */\nexport async function fetchData() {}");
assert_eq!(info.exports.len(), 1);
assert!(info.exports[0].is_public);
}
#[test]
fn jsdoc_public_on_abstract_class_export() {
let info = parse_source("/** @public */\nexport abstract class Base {}");
assert_eq!(info.exports.len(), 1);
assert!(info.exports[0].is_public);
}
#[test]
fn jsdoc_public_star_prefix_in_multiline() {
let info = parse_source(
"/**\n * @param x - the value\n * @returns the result\n * @public\n */\nexport const foo = 1;",
);
assert_eq!(info.exports.len(), 1);
assert!(info.exports[0].is_public);
}
#[test]
fn jsdoc_public_on_type_alias_union() {
let info = parse_source("/** @public */\nexport type Status = 'active' | 'inactive';");
assert_eq!(info.exports.len(), 1);
assert!(info.exports[0].is_public);
}
#[test]
fn jsdoc_api_public_on_function() {
let info = parse_source("/** @api public */\nexport function handler() {}");
assert_eq!(info.exports.len(), 1);
assert!(info.exports[0].is_public);
}
#[test]
fn jsdoc_api_private_does_not_set_public() {
let info = parse_source("/** @api private */\nexport const foo = 1;");
assert_eq!(info.exports.len(), 1);
assert!(!info.exports[0].is_public);
}
#[test]
fn jsdoc_public_not_leaked_across_statements() {
let source = "/** @public */\nconst internal = 1;\nexport const foo = internal;";
let info = parse_source(source);
assert_eq!(info.exports.len(), 1);
assert!(!info.exports[0].is_public);
}
#[test]
fn jsdoc_public_tag_marks_export_public() {
let info = parse_source(
r"/** @public */
export const foo = 1;",
);
assert_eq!(info.exports.len(), 1);
assert!(
info.exports[0].is_public,
"Export with @public JSDoc tag should be marked as public"
);
}
#[test]
fn jsdoc_api_public_tag_marks_export_public() {
let info = parse_source(
r"/** @api public */
export const bar = 2;",
);
assert_eq!(info.exports.len(), 1);
assert!(
info.exports[0].is_public,
"Export with @api public tag should be marked as public"
);
}
#[test]
fn jsdoc_no_public_tag_not_marked() {
let info = parse_source(
r"/** Regular comment */
export const baz = 3;",
);
assert_eq!(info.exports.len(), 1);
assert!(
!info.exports[0].is_public,
"Export without @public tag should not be marked as public"
);
}
#[test]
fn jsdoc_public_partial_word_not_matched() {
let info = parse_source(
r"/** @publicize this */
export const qux = 4;",
);
assert_eq!(info.exports.len(), 1);
assert!(
!info.exports[0].is_public,
"@publicize should not match @public (it's followed by an ident char)"
);
}
#[test]
fn jsdoc_public_on_function_export() {
let info = parse_source(
r"/** @public */
export function myFunc() { return 1; }",
);
let f = info
.exports
.iter()
.find(|e| matches!(&e.name, ExportName::Named(n) if n == "myFunc"));
assert!(f.is_some());
assert!(
f.unwrap().is_public,
"Function export with @public should be marked as public"
);
}
#[test]
fn jsdoc_public_on_class_export() {
let info = parse_source(
r"/** @public */
export class MyClass { doWork() {} }",
);
let c = info
.exports
.iter()
.find(|e| matches!(&e.name, ExportName::Named(n) if n == "MyClass"));
assert!(c.is_some());
assert!(c.unwrap().is_public);
}
#[test]
fn export_without_jsdoc_not_public() {
let info = parse_source("export const plain = 42;");
assert_eq!(info.exports.len(), 1);
assert!(!info.exports[0].is_public);
}