feat(linter/tree-shaking): support UnaryExpression (#3153)

This commit is contained in:
Wang Wenzhe 2024-05-03 23:17:15 +08:00 committed by GitHub
parent 56a0db8621
commit 5c21b7f843
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 410 additions and 50 deletions

View file

@ -1,5 +1,3 @@
use std::cell::{Cell, RefCell};
use oxc_ast::{
ast::{
match_declaration, match_expression, match_member_expression,
@ -21,12 +19,13 @@ use oxc_semantic::{AstNode, SymbolId};
use oxc_span::{GetSpan, Span};
use oxc_syntax::operator::{LogicalOperator, UnaryOperator};
use rustc_hash::FxHashSet;
use std::{cell::Cell, cell::RefCell};
use crate::{
ast_util::{get_declaration_of_variable, get_symbol_id_of_variable},
utils::{
calculate_binary_operation, calculate_logical_operation, get_write_expr,
has_comment_about_side_effect_check, has_pure_notation, no_effects, Value,
calculate_binary_operation, calculate_logical_operation, calculate_unary_operation,
get_write_expr, has_comment_about_side_effect_check, has_pure_notation, no_effects, Value,
},
LintContext,
};
@ -117,6 +116,9 @@ impl<'a> ListenerMap for Statement<'a> {
finalizer.body.iter().for_each(|stmt| stmt.report_effects(options));
});
}
Self::ThrowStatement(stmt) => {
options.ctx.diagnostic(NoSideEffectsDiagnostic::Throw(stmt.span));
}
Self::BlockStatement(stmt) => {
stmt.body.iter().for_each(|stmt| stmt.report_effects(options));
}
@ -528,6 +530,9 @@ impl<'a> ListenerMap for Expression<'a> {
Self::UnaryExpression(expr) => {
expr.get_value_and_report_effects(options);
}
Self::UpdateExpression(expr) => {
expr.argument.report_effects_when_assigned(options);
}
Self::ArrowFunctionExpression(_)
| Self::FunctionExpression(_)
| Self::Identifier(_)
@ -644,12 +649,13 @@ impl<'a> ListenerMap for UnaryExpression<'a> {
Expression::PrivateFieldExpression(expr) => {
expr.object.report_effects_when_mutated(options);
}
_ => {}
_ => options.ctx.diagnostic(NoSideEffectsDiagnostic::Delete(self.argument.span())),
}
return Value::Unknown;
}
// TODO
Value::Unknown
let value = self.argument.get_value_and_report_effects(options);
calculate_unary_operation(self.operator, value)
}
}

View file

@ -69,6 +69,14 @@ enum NoSideEffectsDiagnostic {
#[error("eslint-plugin-tree-shaking(no-side-effects-in-initialization): Debugger statements are side-effects")]
#[diagnostic(severity(warning))]
Debugger(#[label] Span),
#[error("eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of deleting anything but a MemberExpression")]
#[diagnostic(severity(warning))]
Delete(#[label] Span),
#[error("eslint-plugin-tree-shaking(no-side-effects-in-initialization): Throwing an error is a side-effect")]
#[diagnostic(severity(warning))]
Throw(#[label] Span),
}
/// <https://github.com/lukastaegert/eslint-plugin-tree-shaking/blob/master/src/rules/no-side-effects-in-initialization.ts>
@ -307,12 +315,12 @@ fn test() {
// MemberExpression when mutated
"const x = {};x.y = ext",
"const x = {y: 1};delete x.y",
// // MetaProperty
// "function x(){const y = new.target}; x()",
// // MethodDefinition
// "class x {a(){}}",
// "class x {static a(){}}",
// // NewExpression
// MetaProperty
"function x(){const y = new.target}; x()",
// MethodDefinition
"class x {a(){}}",
"class x {static a(){}}",
// NewExpression
"const x = new (function (){this.x = 1})()",
"function x(){this.y = 1}; const z = new x()",
"/*@__PURE__*/ new ext()",
@ -351,17 +359,17 @@ fn test() {
"const y = new (function (){{this.x = 1}})()",
"const y = new (function (){(()=>{this.x = 1})()})()",
"function x(){this.y = 1}; const y = new x()",
// // TryStatement
// "try {} catch (error) {}",
// "try {} finally {}",
// "try {} catch (error) {} finally {}",
// // UnaryExpression
// "!ext",
// "const x = {};delete x.y",
// r#"const x = {};delete x["y"]"#,
// // UpdateExpression
// "let x=1;x++",
// "const x = {};x.y++",
// TryStatement
"try {} catch (error) {}",
"try {} finally {}",
"try {} catch (error) {} finally {}",
// UnaryExpression
"!ext",
"const x = {};delete x.y",
r#"const x = {};delete x["y"]"#,
// UpdateExpression
"let x=1;x++",
"const x = {};x.y++",
// // VariableDeclaration
// "const x = 1",
// // VariableDeclarator
@ -535,7 +543,7 @@ fn test() {
"const x = ()=>{}; const {y} = x(); y()",
"const x = ()=>{}; const [y] = x(); y()",
// // Identifier when mutated
// "var x = ext; x.y = 1",
"var x = ext; x.y = 1",
// "var x = {}; x = ext; x.y = 1",
// "var x = {}; var x = ext; x.y = 1",
// "var x = {}; x = ext; x.y = 1; x.y = 1; x.y = 1",
@ -596,8 +604,8 @@ fn test() {
"const x = {y: ext};x.y.z = 1",
"const x = {y:ext};const y = x.y; y.z = 1",
"const x = {y: ext};delete x.y.z",
// // MethodDefinition
// "class x {static [ext()](){}}",
// MethodDefinition
"class x {static [ext()](){}}",
// NewExpression
"const x = new ext()",
"new ext()",
@ -636,19 +644,19 @@ fn test() {
"(function(){this.x = 1}())",
"const y = new (function (){(function(){this.x = 1}())})()",
"function x(){this.y = 1}; x()",
// // ThrowStatement
// r#"throw new Error("Hello Error")"#,
// // TryStatement
// "try {ext()} catch (error) {}",
// "try {} finally {ext()}",
// // UnaryExpression
// "!ext()",
// "delete ext.x",
// r#"delete ext["x"]"#,
// "const x = ()=>{};delete x()",
// // UpdateExpression
// "ext++",
// "const x = {};x[ext()]++",
// ThrowStatement
r#"throw new Error("Hello Error")"#,
// TryStatement
"try {ext()} catch (error) {}",
"try {} finally {ext()}",
// UnaryExpression
"!ext()",
"delete ext.x",
r#"delete ext["x"]"#,
"const x = ()=>{};delete x()",
// UpdateExpression
"ext++",
"const x = {};x[ext()]++",
// // VariableDeclaration
// "const x = ext()",
// // VariableDeclarator

View file

@ -734,6 +734,12 @@ expression: no_side_effects_in_initialization
· ───
╰────
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of mutating `ext`
╭─[no_side_effects_in_initialization.tsx:1:9]
1 │ var x = ext; x.y = 1
· ───
╰────
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling global function `ext`
╭─[no_side_effects_in_initialization.tsx:1:5]
1 │ if (ext()>0){}
@ -992,6 +998,12 @@ expression: no_side_effects_in_initialization
· ───
╰────
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling global function `ext`
╭─[no_side_effects_in_initialization.tsx:1:18]
1 │ class x {static [ext()](){}}
· ───
╰────
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling global function `ext`
╭─[no_side_effects_in_initialization.tsx:1:15]
1 │ const x = new ext()
@ -1033,3 +1045,57 @@ expression: no_side_effects_in_initialization
1 │ function x(){this.y = 1}; x()
· ────
╰────
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Throwing an error is a side-effect
╭─[no_side_effects_in_initialization.tsx:1:1]
1 │ throw new Error("Hello Error")
· ──────────────────────────────
╰────
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling global function `ext`
╭─[no_side_effects_in_initialization.tsx:1:6]
1 │ try {ext()} catch (error) {}
· ───
╰────
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling global function `ext`
╭─[no_side_effects_in_initialization.tsx:1:17]
1 │ try {} finally {ext()}
· ───
╰────
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling global function `ext`
╭─[no_side_effects_in_initialization.tsx:1:2]
1 │ !ext()
· ───
╰────
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of mutating `ext`
╭─[no_side_effects_in_initialization.tsx:1:8]
1 │ delete ext.x
· ───
╰────
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of mutating `ext`
╭─[no_side_effects_in_initialization.tsx:1:8]
1 │ delete ext["x"]
· ───
╰────
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of deleting anything but a MemberExpression
╭─[no_side_effects_in_initialization.tsx:1:25]
1 │ const x = ()=>{};delete x()
· ───
╰────
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of assignment to `ext`
╭─[no_side_effects_in_initialization.tsx:1:1]
1 │ ext++
· ───
╰────
⚠ eslint-plugin-tree-shaking(no-side-effects-in-initialization): Cannot determine side-effects of calling global function `ext`
╭─[no_side_effects_in_initialization.tsx:1:16]
1 │ const x = {};x[ext()]++
· ───
╰────

View file

@ -1,7 +1,7 @@
use oxc_ast::{ast::Expression, AstKind, CommentKind};
use oxc_semantic::AstNodeId;
use oxc_span::Span;
use oxc_syntax::operator::{BinaryOperator, LogicalOperator};
use oxc_syntax::operator::{BinaryOperator, LogicalOperator, UnaryOperator};
use crate::LintContext;
@ -52,6 +52,21 @@ impl Value {
Value::String(str) => Some(!matches!(str, StringValue::Empty)),
}
}
/// If the value is a boolean, return the negation of the boolean, otherwise return `None`.
pub fn neg_bool(&self) -> Option<Value> {
match self {
Value::Boolean(boolean) => Some(Value::Boolean(!*boolean)),
_ => None,
}
}
pub fn to_bool(self) -> Option<bool> {
match self {
Value::Boolean(boolean) => Some(boolean),
Value::Number(num) => Some(num != 0.0),
Value::String(str) => Some(!matches!(str, StringValue::Empty)),
Value::Unknown => None,
}
}
}
pub fn get_write_expr<'a, 'b>(
@ -174,6 +189,8 @@ pub fn get_leading_tree_shaking_comment<'a>(span: Span, ctx: &LintContext<'a>) -
/// Port from <https://github.com/lukastaegert/eslint-plugin-tree-shaking/blob/463fa1f0bef7caa2b231a38b9c3557051f506c92/src/rules/no-side-effects-in-initialization.ts#L136-L161>
/// <https://tc39.es/ecma262/#sec-evaluatestringornumericbinaryexpression>
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_sign_loss)]
pub fn calculate_binary_operation(op: BinaryOperator, left: Value, right: Value) -> Value {
match op {
BinaryOperator::Addition => match (left, right) {
@ -191,6 +208,14 @@ pub fn calculate_binary_operation(op: BinaryOperator, left: Value, right: Value)
(Value::Number(a), Value::Number(b)) => Value::Number(a - b),
_ => Value::Unknown,
},
BinaryOperator::Multiplication => match (left, right) {
(Value::Number(a), Value::Number(b)) => Value::Number(a * b),
_ => Value::Unknown,
},
BinaryOperator::Division => match (left, right) {
(Value::Number(a), Value::Number(b)) => Value::Number(a / b),
_ => Value::Unknown,
},
// <https://tc39.es/ecma262/#sec-islessthan>
BinaryOperator::LessThan => match (left, right) {
// <https://tc39.es/ecma262/#sec-numeric-types-number-lessThan>
@ -200,24 +225,131 @@ pub fn calculate_binary_operation(op: BinaryOperator, left: Value, right: Value)
(Value::Number(a), Value::Number(b)) => Value::Boolean(a < b),
_ => Value::Unknown,
},
_ => Value::Unknown,
BinaryOperator::GreaterEqualThan => {
calculate_binary_operation(BinaryOperator::LessThan, left, right)
.neg_bool()
.unwrap_or(Value::Unknown)
}
BinaryOperator::Equality => match (left, right) {
(Value::Number(a), Value::Number(b)) => Value::Boolean((a - b).abs() < f64::EPSILON),
_ => Value::Unknown,
},
BinaryOperator::Inequality => {
calculate_binary_operation(BinaryOperator::Equality, left, right)
.neg_bool()
.unwrap_or(Value::Unknown)
}
BinaryOperator::StrictEquality => match (left, right) {
(Value::Number(a), Value::Number(b)) => Value::Boolean((a - b).abs() < f64::EPSILON),
(Value::Boolean(a), Value::Boolean(b)) => Value::Boolean(a == b),
_ => Value::Unknown,
},
BinaryOperator::StrictInequality => {
calculate_binary_operation(BinaryOperator::StrictEquality, left, right)
.neg_bool()
.unwrap_or(Value::Unknown)
}
BinaryOperator::LessEqualThan => match (left, right) {
(Value::Number(a), Value::Number(b)) => Value::Boolean(a <= b),
_ => Value::Unknown,
},
BinaryOperator::GreaterThan => {
calculate_binary_operation(BinaryOperator::LessEqualThan, left, right)
.neg_bool()
.unwrap_or(Value::Unknown)
}
BinaryOperator::ShiftRightZeroFill => match (left, right) {
(Value::Number(a), Value::Number(b)) => {
Value::Number(f64::from((a as u32) >> (b as u32)))
}
_ => Value::Unknown,
},
BinaryOperator::Remainder => match (left, right) {
(Value::Number(a), Value::Number(b)) => Value::Number(a % b),
_ => Value::Unknown,
},
BinaryOperator::BitwiseOR => match (left, right) {
(Value::Number(a), Value::Number(b)) => Value::Number(f64::from(a as i32 | b as i32)),
_ => Value::Unknown,
},
BinaryOperator::BitwiseXOR => match (left, right) {
(Value::Number(a), Value::Number(b)) => Value::Number(f64::from(a as i32 ^ b as i32)),
_ => Value::Unknown,
},
BinaryOperator::BitwiseAnd => match (left, right) {
(Value::Number(a), Value::Number(b)) => Value::Number(f64::from(a as i32 & b as i32)),
_ => Value::Unknown,
},
BinaryOperator::Exponential => match (left, right) {
(Value::Number(a), Value::Number(b)) => Value::Number(a.powf(b)),
_ => Value::Unknown,
},
BinaryOperator::ShiftLeft => match (left, right) {
(Value::Number(a), Value::Number(b)) => {
Value::Number(f64::from((a as i32) << (b as i32)))
}
_ => Value::Unknown,
},
BinaryOperator::ShiftRight => match (left, right) {
(Value::Number(a), Value::Number(b)) => Value::Number(f64::from(a as i32 >> b as i32)),
_ => Value::Unknown,
},
BinaryOperator::In | BinaryOperator::Instanceof => Value::Unknown,
}
}
/// <https://tc39.es/ecma262/#sec-binary-logical-operators-runtime-semantics-evaluation>
pub fn calculate_logical_operation(op: LogicalOperator, left: Value, right: Value) -> Value {
match op {
LogicalOperator::And => match (left, right) {
(Value::Boolean(a), Value::Boolean(b)) => Value::Boolean(a && b),
_ => Value::Unknown,
},
LogicalOperator::Or => match (left, right) {
(Value::Boolean(a), Value::Boolean(b)) => Value::Boolean(a || b),
_ => Value::Unknown,
},
LogicalOperator::And => {
let left = left.to_bool();
let right = right.to_bool();
match (left, right) {
(Some(false), _) | (_, Some(false)) => Value::Boolean(false),
(Some(true), Some(true)) => Value::Boolean(true),
_ => Value::Unknown,
}
}
LogicalOperator::Or => {
let left = left.to_bool();
let right = right.to_bool();
match (left, right) {
(Some(true), _) | (_, Some(true)) => Value::Boolean(true),
(Some(false), Some(false)) => Value::Boolean(false),
_ => Value::Unknown,
}
}
LogicalOperator::Coalesce => Value::Unknown,
}
}
#[allow(clippy::cast_possible_truncation)]
pub fn calculate_unary_operation(op: UnaryOperator, value: Value) -> Value {
match op {
UnaryOperator::UnaryNegation => match value {
Value::Number(num) => Value::Number(-num),
_ => Value::Unknown,
},
UnaryOperator::UnaryPlus => match value {
Value::Number(num) => Value::Number(num),
_ => Value::Unknown,
},
UnaryOperator::LogicalNot => match value {
Value::Boolean(boolean) => Value::Boolean(!boolean),
_ => Value::Unknown,
},
UnaryOperator::BitwiseNot => match value {
Value::Number(num) => Value::Number(f64::from(!(num as i32))),
_ => Value::Unknown,
},
UnaryOperator::Typeof => Value::String(StringValue::NonEmpty),
UnaryOperator::Void | UnaryOperator::Delete => Value::Unknown,
}
}
#[test]
fn test_calculate_binary_operation() {
use oxc_syntax::operator::BinaryOperator;
@ -245,8 +377,156 @@ fn test_calculate_binary_operation() {
let op = BinaryOperator::Subtraction;
assert_eq!(fun(op, Value::Number(1.0), Value::Number(2.0),), Value::Number(-1.0));
// "*",
let op = BinaryOperator::Multiplication;
assert_eq!(fun(op, Value::Number(4.0), Value::Number(2.0),), Value::Number(8.0));
// "/",
let op = BinaryOperator::Division;
assert_eq!(fun(op, Value::Number(4.0), Value::Number(2.0),), Value::Number(2.0));
// "<"
let op = BinaryOperator::LessThan;
assert_eq!(fun(op, Value::Number(1.0), Value::Number(2.0),), Value::Boolean(true));
assert_eq!(fun(op, Value::Unknown, Value::Number(2.0),), Value::Boolean(false));
// ">=",
let op = BinaryOperator::GreaterEqualThan;
assert_eq!(fun(op, Value::Number(1.0), Value::Number(2.0),), Value::Boolean(false));
assert_eq!(fun(op, Value::Number(2.0), Value::Number(2.0),), Value::Boolean(true));
assert_eq!(fun(op, Value::Number(3.0), Value::Number(2.0),), Value::Boolean(true));
// "==",
let op = BinaryOperator::Equality;
assert_eq!(fun(op, Value::Number(1.0), Value::Number(2.0),), Value::Boolean(false));
assert_eq!(fun(op, Value::Number(1.0), Value::Number(1.0),), Value::Boolean(true));
// "!=",
let op = BinaryOperator::Inequality;
assert_eq!(fun(op, Value::Number(1.0), Value::Number(2.0),), Value::Boolean(true));
assert_eq!(fun(op, Value::Number(1.0), Value::Number(1.0),), Value::Boolean(false));
// "===",
let op = BinaryOperator::StrictEquality;
assert_eq!(fun(op, Value::Number(1.0), Value::Number(2.0),), Value::Boolean(false));
assert_eq!(fun(op, Value::Number(1.0), Value::Number(1.0),), Value::Boolean(true));
// "!==",
let op = BinaryOperator::StrictInequality;
assert_eq!(fun(op, Value::Number(1.0), Value::Number(2.0),), Value::Boolean(true));
assert_eq!(fun(op, Value::Number(1.0), Value::Number(1.0),), Value::Boolean(false));
// "<=",
let op = BinaryOperator::LessEqualThan;
assert_eq!(fun(op, Value::Number(1.0), Value::Number(2.0),), Value::Boolean(true));
assert_eq!(fun(op, Value::Number(2.0), Value::Number(2.0),), Value::Boolean(true));
assert_eq!(fun(op, Value::Number(3.0), Value::Number(2.0),), Value::Boolean(false));
// ">",
let op = BinaryOperator::GreaterThan;
assert_eq!(fun(op, Value::Number(1.0), Value::Number(2.0),), Value::Boolean(false));
assert_eq!(fun(op, Value::Number(2.0), Value::Number(2.0),), Value::Boolean(false));
assert_eq!(fun(op, Value::Number(3.0), Value::Number(2.0),), Value::Boolean(true));
// "<<",
let op = BinaryOperator::ShiftLeft;
assert_eq!(fun(op, Value::Number(1.0), Value::Number(2.0),), Value::Number(4.0));
// ">>",
let op = BinaryOperator::ShiftRight;
assert_eq!(fun(op, Value::Number(4.0), Value::Number(2.0),), Value::Number(1.0));
// ">>>",
let op = BinaryOperator::ShiftRightZeroFill;
assert_eq!(fun(op, Value::Number(4.0), Value::Number(2.0),), Value::Number(1.0));
// "%",
let op = BinaryOperator::Remainder;
assert_eq!(fun(op, Value::Number(4.0), Value::Number(2.0),), Value::Number(0.0));
// "|",
let op = BinaryOperator::BitwiseOR;
assert_eq!(fun(op, Value::Number(1.0), Value::Number(2.0),), Value::Number(3.0));
// "^",
let op = BinaryOperator::BitwiseXOR;
assert_eq!(fun(op, Value::Number(1.0), Value::Number(2.0),), Value::Number(3.0));
// "&",
let op = BinaryOperator::BitwiseAnd;
assert_eq!(fun(op, Value::Number(1.0), Value::Number(2.0),), Value::Number(0.0));
// "in",
let op = BinaryOperator::In;
assert_eq!(fun(op, Value::Unknown, Value::Number(2.0),), Value::Unknown);
// "instanceof",
let op = BinaryOperator::Instanceof;
assert_eq!(fun(op, Value::Unknown, Value::Number(2.0),), Value::Unknown);
// "**",
let op = BinaryOperator::Exponential;
assert_eq!(fun(op, Value::Number(2.0), Value::Number(3.0),), Value::Number(8.0));
}
#[test]
fn test_logical_operation() {
use oxc_syntax::operator::LogicalOperator;
let fun = calculate_logical_operation;
// "&&"
let op = LogicalOperator::And;
assert_eq!(fun(op, Value::Boolean(true), Value::Boolean(true)), Value::Boolean(true));
assert_eq!(fun(op, Value::Boolean(true), Value::Boolean(false)), Value::Boolean(false));
assert_eq!(fun(op, Value::Boolean(false), Value::Boolean(true)), Value::Boolean(false));
assert_eq!(fun(op, Value::Boolean(false), Value::Boolean(false)), Value::Boolean(false));
assert_eq!(fun(op, Value::Unknown, Value::Boolean(true)), Value::Unknown);
assert_eq!(fun(op, Value::Boolean(true), Value::Unknown), Value::Unknown);
assert_eq!(fun(op, Value::Unknown, Value::Unknown), Value::Unknown);
// "||"
let op = LogicalOperator::Or;
assert_eq!(fun(op, Value::Boolean(true), Value::Boolean(true)), Value::Boolean(true));
assert_eq!(fun(op, Value::Boolean(true), Value::Boolean(false)), Value::Boolean(true));
assert_eq!(fun(op, Value::Boolean(false), Value::Boolean(true)), Value::Boolean(true));
assert_eq!(fun(op, Value::Boolean(false), Value::Boolean(false)), Value::Boolean(false));
assert_eq!(fun(op, Value::Unknown, Value::Boolean(true)), Value::Boolean(true));
assert_eq!(fun(op, Value::Boolean(true), Value::Unknown), Value::Boolean(true));
assert_eq!(fun(op, Value::Unknown, Value::Unknown), Value::Unknown);
// "??"
let op = LogicalOperator::Coalesce;
assert_eq!(fun(op, Value::Boolean(true), Value::Boolean(true)), Value::Unknown);
}
#[test]
fn test_unary_operation() {
use oxc_syntax::operator::UnaryOperator;
let fun = calculate_unary_operation;
// "-"
let op = UnaryOperator::UnaryNegation;
assert_eq!(fun(op, Value::Number(1.0)), Value::Number(-1.0));
assert_eq!(fun(op, Value::Boolean(true)), Value::Unknown);
// "+"
let op = UnaryOperator::UnaryPlus;
assert_eq!(fun(op, Value::Number(1.0)), Value::Number(1.0));
assert_eq!(fun(op, Value::Boolean(true)), Value::Unknown);
// "!"
let op = UnaryOperator::LogicalNot;
assert_eq!(fun(op, Value::Boolean(true)), Value::Boolean(false));
assert_eq!(fun(op, Value::Number(1.0)), Value::Unknown);
// "~"
let op = UnaryOperator::BitwiseNot;
assert_eq!(fun(op, Value::Number(1.0)), Value::Number(-2.0));
assert_eq!(fun(op, Value::Boolean(true)), Value::Unknown);
// "typeof"
let op = UnaryOperator::Typeof;
assert_eq!(fun(op, Value::Number(1.0)), Value::String(StringValue::NonEmpty));
assert_eq!(fun(op, Value::Boolean(true)), Value::String(StringValue::NonEmpty));
}