use crate::checker::context::CheckerOptions;
use crate::checker::state::CheckerState;
use crate::test_fixtures::TestContext;
use std::sync::Arc;
use tsz_binder::BinderState;
use tsz_parser::parser::ParserState;
use tsz_solver::TypeInterner;
const GLOBAL_TYPE_MOCKS: &str = r#"
interface Array<T> {}
interface String {}
interface Boolean {}
interface Number {}
interface Object {}
interface Function {}
interface RegExp {}
interface IArguments {}
"#;
fn test_function_variance(source: &str, expected_error_code: u32) {
let source_clean = source.replace("// @strictFunctionTypes: true", "");
let source_clean = source_clean.trim();
let source = format!("// @strictFunctionTypes: true\n{GLOBAL_TYPE_MOCKS}\n{source_clean}");
let ctx = TestContext::new();
let mut parser = ParserState::new("test.ts".to_string(), source);
let root = parser.parse_source_file();
let mut binder = BinderState::new();
binder.bind_source_file_with_libs(parser.get_arena(), root, &ctx.lib_files);
let types = TypeInterner::new();
let mut checker = CheckerState::new(
parser.get_arena(),
&binder,
&types,
"test.ts".to_string(),
CheckerOptions::default(),
);
if !ctx.lib_files.is_empty() {
let lib_contexts: Vec<crate::checker::context::LibContext> = ctx
.lib_files
.iter()
.map(|lib| crate::checker::context::LibContext {
arena: Arc::clone(&lib.arena),
binder: Arc::clone(&lib.binder),
})
.collect();
checker.ctx.set_lib_contexts(lib_contexts);
}
checker.check_source_file(root);
let error_count = checker
.ctx
.diagnostics
.iter()
.filter(|d| d.code == expected_error_code)
.count();
assert!(
error_count >= 1,
"Expected at least 1 TS{} error, got {}: {:?}",
expected_error_code,
error_count,
checker.ctx.diagnostics
);
}
fn test_no_errors(source: &str) {
let source_clean = source.replace("// @strictFunctionTypes: true", "");
let source_clean = source_clean.trim();
let source = format!("// @strictFunctionTypes: true\n{GLOBAL_TYPE_MOCKS}\n{source_clean}");
let ctx = TestContext::new();
let mut parser = ParserState::new("test.ts".to_string(), source);
let root = parser.parse_source_file();
let mut binder = BinderState::new();
binder.bind_source_file_with_libs(parser.get_arena(), root, &ctx.lib_files);
let types = TypeInterner::new();
let mut checker = CheckerState::new(
parser.get_arena(),
&binder,
&types,
"test.ts".to_string(),
CheckerOptions::default(),
);
if !ctx.lib_files.is_empty() {
let lib_contexts: Vec<crate::checker::context::LibContext> = ctx
.lib_files
.iter()
.map(|lib| crate::checker::context::LibContext {
arena: Arc::clone(&lib.arena),
binder: Arc::clone(&lib.binder),
})
.collect();
checker.ctx.set_lib_contexts(lib_contexts);
}
checker.check_source_file(root);
let errors: Vec<_> = checker
.ctx
.diagnostics
.iter()
.filter(|d| {
d.category == crate::checker::diagnostics::DiagnosticCategory::Error && d.code != 2318
})
.collect();
assert!(
errors.is_empty(),
"Expected no errors, got {}: {:?}",
errors.len(),
errors
);
}
#[test]
fn test_method_bivariance_same_params() {
test_no_errors(
r#"
interface A {
method(x: number): void;
}
interface B {
method(x: number): void;
}
let a: A = { method: (x: number) => {} };
let b: B = a;
"#,
);
}
#[test]
fn test_method_bivariance_wider_param() {
test_no_errors(
r#"
interface A {
method(x: number): void;
}
interface B {
method(x: number | string): void;
}
let a: A = { method: (x: number | string) => {} };
let b: B = a;
"#,
);
}
#[test]
fn test_function_property_contravariance() {
test_function_variance(
r#"
// @strictFunctionTypes: true: true
interface A {
prop: (x: number | string) => void;
}
interface B {
prop: (x: number) => void;
}
let b: B = { prop: (x: number) => {} };
let a: A = b;
"#,
2322, );
}
#[test]
fn test_arrow_function_property_contravariance() {
test_function_variance(
r#"
// @strictFunctionTypes: true: true
interface A {
prop: (x: number) => void;
}
interface B {
prop: (x: number | string) => void;
}
let b: B = { prop: (x: number) => {} };
let a: A = b;
"#,
2322, );
}
#[test]
fn test_method_shorthand_bivariant() {
test_no_errors(
r#"
// @strictFunctionTypes: true
interface A {
method(x: number): void;
}
interface B {
method(x: number | string): void;
}
let b: B = { method: (x: number | string) => {} };
let a: A = b;
"#,
);
}
#[test]
fn test_method_bivariance_strict_mode() {
test_no_errors(
r#"
// @strictFunctionTypes: true
interface A {
method(x: number): void;
}
interface B {
method(x: number | string): void;
}
let b: B = { method: (x: number | string) => {} };
let a: A = b;
"#,
);
}
#[test]
fn test_function_property_contravariance_strict_mode() {
test_function_variance(
r#"
// @strictFunctionTypes: true
interface A {
prop: (x: number | string) => void;
}
interface B {
prop: (x: number) => void;
}
let b: B = { prop: (x: number) => {} };
let a: A = b;
"#,
2322, );
}