use crate::RuleId;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub struct CallRecognition {
pub receiver: Option<&'static str>,
pub member: &'static str,
pub rule: RuleId,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum TypeScriptReceiverKind {
Map,
Set,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub struct MethodRecognition {
pub receiver: TypeScriptReceiverKind,
pub member: &'static str,
pub rule: RuleId,
}
pub const TYPESCRIPT_CALLS: &[CallRecognition] = &[
free("fetch", RuleId::TsFetch),
free("structuredClone", RuleId::TsStructuredClone),
free("String", RuleId::TsPrimitiveCast),
free("Number", RuleId::TsPrimitiveCast),
free("Boolean", RuleId::TsPrimitiveCast),
free("BigInt", RuleId::TsPrimitiveCast),
free("parseFloat", RuleId::TsPrimitiveCast),
free("parseInt", RuleId::TsPrimitiveCast),
free("isNaN", RuleId::TsNumberPredicate),
free("Symbol", RuleId::TsSymbol),
static_call("JSON", "stringify", RuleId::TsJsonStringify),
static_call("JSON", "parse", RuleId::TsJsonParse),
static_call("Date", "now", RuleId::TsDateNow),
static_call("Math", "random", RuleId::TsMathRandom),
static_call("Math", "abs", RuleId::TsMathNumeric),
static_call("Math", "floor", RuleId::TsMathNumeric),
static_call("Math", "ceil", RuleId::TsMathNumeric),
static_call("Math", "round", RuleId::TsMathNumeric),
static_call("Math", "trunc", RuleId::TsMathNumeric),
static_call("Math", "max", RuleId::TsMathNumeric),
static_call("Math", "min", RuleId::TsMathNumeric),
static_call("Math", "hypot", RuleId::TsMathNumeric),
static_call("Math", "sqrt", RuleId::TsMathNumeric),
static_call("Math", "cbrt", RuleId::TsMathNumeric),
static_call("Math", "sign", RuleId::TsMathNumeric),
static_call("Math", "sin", RuleId::TsMathNumeric),
static_call("Math", "cos", RuleId::TsMathNumeric),
static_call("Math", "tan", RuleId::TsMathNumeric),
static_call("Math", "asin", RuleId::TsMathNumeric),
static_call("Math", "acos", RuleId::TsMathNumeric),
static_call("Math", "atan", RuleId::TsMathNumeric),
static_call("Math", "log", RuleId::TsMathNumeric),
static_call("Math", "log10", RuleId::TsMathNumeric),
static_call("Math", "log2", RuleId::TsMathNumeric),
static_call("Math", "exp", RuleId::TsMathNumeric),
static_call("Math", "pow", RuleId::TsMathNumeric),
static_call("Math", "atan2", RuleId::TsMathNumeric),
static_call("Number", "isFinite", RuleId::TsNumberPredicate),
static_call("Number", "isInteger", RuleId::TsNumberPredicate),
static_call("Number", "isNaN", RuleId::TsNumberPredicate),
static_call("Number", "parseFloat", RuleId::TsNumberParseFloat),
static_call("Number", "parseInt", RuleId::TsNumberParseInt),
static_call("Promise", "resolve", RuleId::TsPromiseStatic),
static_call("Promise", "all", RuleId::TsPromiseStatic),
static_call("Promise", "race", RuleId::TsPromiseStatic),
static_call("Promise", "allSettled", RuleId::TsPromiseStatic),
static_call("Symbol", "for", RuleId::TsSymbol),
static_call("Object", "is", RuleId::TsObjectStatic),
static_call("Object", "fromEntries", RuleId::TsObjectStatic),
static_call("Object", "create", RuleId::TsObjectStatic),
static_call("Object", "getPrototypeOf", RuleId::TsObjectStatic),
static_call("Array", "isArray", RuleId::TsArrayStatic),
static_call("Array", "from", RuleId::TsArrayStatic),
static_call("ArrayBuffer", "isView", RuleId::TsArrayStatic),
static_call("Buffer", "from", RuleId::TsBufferStatic),
static_call("Buffer", "alloc", RuleId::TsBufferStatic),
static_call("Buffer", "concat", RuleId::TsBufferStatic),
static_call("Buffer", "isBuffer", RuleId::TsBufferStatic),
];
pub const TYPESCRIPT_METHODS: &[MethodRecognition] = &[
method(TypeScriptReceiverKind::Map, "has", RuleId::TsMapHas),
method(TypeScriptReceiverKind::Map, "get", RuleId::TsMapGet),
method(TypeScriptReceiverKind::Map, "set", RuleId::TsMapMutation),
method(TypeScriptReceiverKind::Map, "delete", RuleId::TsMapMutation),
method(TypeScriptReceiverKind::Map, "clear", RuleId::TsMapMutation),
method(TypeScriptReceiverKind::Map, "keys", RuleId::TsMapProjection),
method(
TypeScriptReceiverKind::Map,
"values",
RuleId::TsMapProjection,
),
method(
TypeScriptReceiverKind::Map,
"entries",
RuleId::TsMapProjection,
),
method(TypeScriptReceiverKind::Set, "has", RuleId::TsSetHas),
method(TypeScriptReceiverKind::Set, "add", RuleId::TsSetMutation),
method(TypeScriptReceiverKind::Set, "delete", RuleId::TsSetMutation),
method(TypeScriptReceiverKind::Set, "clear", RuleId::TsSetMutation),
method(TypeScriptReceiverKind::Set, "keys", RuleId::TsSetProjection),
method(
TypeScriptReceiverKind::Set,
"values",
RuleId::TsSetProjection,
),
method(
TypeScriptReceiverKind::Set,
"entries",
RuleId::TsSetProjection,
),
];
#[must_use]
pub fn typescript_call_rule(receiver: Option<&str>, member: &str) -> Option<RuleId> {
TYPESCRIPT_CALLS
.iter()
.find(|entry| entry.receiver == receiver && entry.member == member)
.map(|entry| entry.rule)
}
#[must_use]
pub fn typescript_method_rule(receiver: TypeScriptReceiverKind, member: &str) -> Option<RuleId> {
TYPESCRIPT_METHODS
.iter()
.find(|entry| entry.receiver == receiver && entry.member == member)
.map(|entry| entry.rule)
}
const fn free(member: &'static str, rule: RuleId) -> CallRecognition {
CallRecognition {
receiver: None,
member,
rule,
}
}
const fn static_call(
receiver: &'static str,
member: &'static str,
rule: RuleId,
) -> CallRecognition {
CallRecognition {
receiver: Some(receiver),
member,
rule,
}
}
const fn method(
receiver: TypeScriptReceiverKind,
member: &'static str,
rule: RuleId,
) -> MethodRecognition {
MethodRecognition {
receiver,
member,
rule,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn recognizes_exact_typescript_calls() {
let cases = [
((None, "structuredClone"), RuleId::TsStructuredClone),
((Some("JSON"), "parse"), RuleId::TsJsonParse),
((Some("Math"), "floor"), RuleId::TsMathNumeric),
((Some("Number"), "isNaN"), RuleId::TsNumberPredicate),
((Some("Promise"), "allSettled"), RuleId::TsPromiseStatic),
((Some("Object"), "fromEntries"), RuleId::TsObjectStatic),
((Some("Object"), "create"), RuleId::TsObjectStatic),
((Some("Array"), "isArray"), RuleId::TsArrayStatic),
];
for ((receiver, member), expected) in cases {
assert_eq!(typescript_call_rule(receiver, member), Some(expected));
}
}
#[test]
fn rejects_similar_unsupported_typescript_calls() {
let cases = [
(None, "fetcher"),
(Some("json"), "parse"),
(Some("Math"), "randomBytes"),
(Some("Number"), "parseDouble"),
(Some("Promise"), "any"),
(Some("Object"), "entries"),
(Some("Array"), "of"),
(Some("value"), "map"),
];
for (receiver, member) in cases {
assert_eq!(typescript_call_rule(receiver, member), None);
}
}
#[test]
fn recognizes_typescript_receiver_methods() {
let cases = [
(TypeScriptReceiverKind::Map, "has", Some(RuleId::TsMapHas)),
(TypeScriptReceiverKind::Map, "get", Some(RuleId::TsMapGet)),
(
TypeScriptReceiverKind::Map,
"set",
Some(RuleId::TsMapMutation),
),
(
TypeScriptReceiverKind::Map,
"entries",
Some(RuleId::TsMapProjection),
),
(TypeScriptReceiverKind::Set, "has", Some(RuleId::TsSetHas)),
(
TypeScriptReceiverKind::Set,
"add",
Some(RuleId::TsSetMutation),
),
(
TypeScriptReceiverKind::Set,
"values",
Some(RuleId::TsSetProjection),
),
(TypeScriptReceiverKind::Set, "get", None),
(TypeScriptReceiverKind::Map, "add", None),
];
for (receiver, member, expected) in cases {
assert_eq!(typescript_method_rule(receiver, member), expected);
}
}
}