use super::super::common::*;
use luau_common::flags;
fn export_options() -> ParseOptions {
ParseOptions::default()
}
#[test]
fn parse_attribute_on_export_function_stat() {
let _export = flags::LuauExportValueSyntax.scoped(true);
let _classes = flags::DebugLuauUserDefinedClasses.scoped(true);
with_parse(
r#"
@checked
export function hello(x, y)
return x + y
end
"#,
ParseOptions::default(),
|result| {
let result = result.unwrap();
let [statement] = statement_kinds(result.root.as_slice()).exact();
let local_function = statement
.as_local_function()
.expect("expected exported local function");
assert_eq!(local_function.location().begin, pos!(1, 0));
assert!(local_function.name.is_exported);
assert!(local_function.name.is_const);
assert_eq!(local_function.function.attributes.len(), 1);
assert_eq!(
local_function.function.attributes[0].kind(),
AttributeKind::Checked
);
assert_eq!(
local_function.function.attributes[0].location,
loc!(pos!(1, 0), pos!(1, 8))
);
},
);
}
#[test]
fn export_value_rfc() {
let _export = flags::LuauExportValueSyntax.scoped(true);
let _classes = flags::DebugLuauUserDefinedClasses.scoped(true);
with_parse(
r#"
export local version = "1.0.0"
export const TAU = math.pi * 2
export local settings: Settings = getSettings()
export local a, b, c = 1, 2, 3
export local d
export function add(a: number, b: number): number
return a + b
end
export local f, g
function f()
return g()
end
function g()
return 42
end
local function ret(): (string, number, boolean)
return "heh", 42, false
end
export local x, y, z = ret()
"#,
ParseOptions::default(),
|result| {
let result = result.unwrap();
let statements = statement_kinds(result.root.as_slice());
assert_eq!(statements.len(), 11);
let local = statements[0].as_local().expect("expected exported local");
assert!(local.is_exported);
assert!(!local.is_const);
assert_eq!(local.bindings.len(), 1);
assert!(local.bindings[0].is_exported);
assert!(!local.bindings[0].is_const);
let local = statements[1].as_local().expect("expected exported const");
assert!(local.is_exported);
assert!(local.is_const);
assert_eq!(local.bindings.len(), 1);
assert!(local.bindings[0].is_exported);
assert!(local.bindings[0].is_const);
let local = statements[2]
.as_local()
.expect("expected exported annotated local");
assert!(local.is_exported);
assert!(!local.is_const);
assert_eq!(local.bindings.len(), 1);
assert!(local.bindings[0].annotation.is_some());
let local = statements[3]
.as_local()
.expect("expected exported local group");
assert!(local.is_exported);
assert!(!local.is_const);
assert_eq!(local.bindings.len(), 3);
assert!(
local
.bindings
.iter()
.all(|binding| binding.is_exported && !binding.is_const)
);
let local = statements[4]
.as_local()
.expect("expected exported local declaration");
assert!(local.is_exported);
assert!(!local.is_const);
assert_eq!(local.bindings.len(), 1);
assert!(local.values.is_empty());
assert!(local.bindings[0].is_exported);
let local_function = statements[5]
.as_local_function()
.expect("expected exported function");
assert!(local_function.name.is_exported);
assert!(local_function.name.is_const);
let local = statements[6]
.as_local()
.expect("expected exported forward declarations");
assert!(local.is_exported);
assert!(!local.is_const);
assert!(local.values.is_empty());
assert_eq!(local.bindings.len(), 2);
assert!(
local
.bindings
.iter()
.all(|binding| binding.is_exported && !binding.is_const)
);
assert_eq!(statements[7].tag, StatementTag::FunctionDeclaration);
assert_eq!(statements[8].tag, StatementTag::FunctionDeclaration);
let local = statements[10]
.as_local()
.expect("expected exported multret local group");
assert!(local.is_exported);
assert!(!local.is_const);
assert_eq!(local.bindings.len(), 3);
assert!(
local
.bindings
.iter()
.all(|binding| binding.is_exported && !binding.is_const)
);
},
);
with_parse(
r#"
export type Config = {
debug: boolean,
timeout: number,
}
return {
debug = false,
timeout = 5,
}
"#,
export_options(),
|result| {
result.unwrap();
},
);
}
#[test]
fn export_value_parse_failures() {
let _export = flags::LuauExportValueSyntax.scoped(true);
let _classes = flags::DebugLuauUserDefinedClasses.scoped(true);
for source in [
r#"
export foo = 5
"#,
r#"
export foo
"#,
r#"
function foo()
end
export foo
"#,
r#"
export local function foo()
end
"#,
] {
assert!(!parse_errors_with_options(source, export_options()).is_empty());
}
let duplicate = parse_errors_with_options(
r#"
export local foo = 1
export local foo = 2
"#,
export_options(),
);
let duplicate_message = String::from_utf8_lossy(duplicate.first_error().message.as_bytes());
assert!(duplicate_message.contains("foo"));
for source in [
r#"
export local answer = 42
return {answer = answer}
"#,
r#"
if skip then
return
end
export local answer = 42
"#,
r#"
export class Player
public health: number
function setHealth(self, health: number)
self.health = health
return self
end
function getHealth(self): number
return self.health
end
end
return Player {health = 100}
"#,
] {
parse_errors_with_options(source, export_options()).assert_first_message(
"Exporting values is not compatible with top-level return (export/return conflict)",
);
}
for source in [
r#"
if true then
export local insideIf = 1
end
"#,
r#"
do
export const insideDo = 1
end
"#,
r#"
while true do
export local insideWhile = 1
end
"#,
r#"
repeat
export local insideRepeat = 1
until true
"#,
r#"
for i = 1, 1 do
export local insideFor = i
end
"#,
r#"
local function test()
export local insideFunction = 1
end
"#,
] {
parse_errors_with_options(source, export_options())
.assert_first_message("'export' may only be applied to top-level statements");
}
}
#[test]
fn export_value_parse_edge_cases() {
let _export = flags::LuauExportValueSyntax.scoped(true);
let _classes = flags::DebugLuauUserDefinedClasses.scoped(true);
with_parse(
r#"
export = 5
export += 1
export()
"#,
export_options(),
|result| {
let result = result.unwrap();
let statements = statement_kinds(result.root.as_slice());
assert_eq!(statements.len(), 3);
assert_eq!(statements[0].tag, StatementTag::Assign);
assert_eq!(statements[1].tag, StatementTag::CompoundAssign);
assert_eq!(statements[2].tag, StatementTag::Expression);
},
);
with_parse("export local x = 5", export_options(), |result| {
result.unwrap();
});
with_parse("export const x = 5", export_options(), |result| {
result.unwrap();
});
with_parse(
r#"
export function foo()
end
"#,
export_options(),
|result| {
result.unwrap();
},
);
parse_errors_with_options("export 42", export_options())
.assert_first_message("Incomplete statement: expected assignment or a function call");
parse_errors_with_options("export if true then end", export_options())
.assert_first_message("Incomplete statement: expected assignment or a function call");
parse_errors_with_options("export", export_options())
.assert_first_message("Incomplete statement: expected assignment or a function call");
}