1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.

use std::collections::BTreeSet;
use std::fmt::Debug;

pub type ExitCb = Box<dyn Fn(&str, &str) + Send + Sync>;
pub type WarnCb = Box<dyn Fn(&str) + Send + Sync>;

fn exit(_feature: &str, _api_name: &str) {
  std::process::exit(70);
}

fn warn_legacy_flag(_feature: &str) {}

pub struct FeatureChecker {
  features: BTreeSet<&'static str>,
  // TODO(bartlomieju): remove once we migrate away from `--unstable` flag
  // in the CLI.
  legacy_unstable: bool,
  warn_on_legacy_unstable: bool,
  exit_cb: ExitCb,
  warn_cb: WarnCb,
}

impl Default for FeatureChecker {
  fn default() -> Self {
    Self {
      features: Default::default(),
      legacy_unstable: false,
      warn_on_legacy_unstable: false,
      exit_cb: Box::new(exit),
      warn_cb: Box::new(warn_legacy_flag),
    }
  }
}

impl Debug for FeatureChecker {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    f.debug_struct("FeatureChecker")
      .field("features", &self.features)
      .field("legacy_unstable", &self.legacy_unstable)
      .field("warn_on_legacy_unstable", &self.warn_on_legacy_unstable)
      .finish()
  }
}

impl FeatureChecker {
  pub fn enable_feature(&mut self, feature: &'static str) {
    let inserted = self.features.insert(feature);
    assert!(
      inserted,
      "Trying to enabled a feature that is already enabled {}",
      feature
    );
  }

  pub fn set_exit_cb(&mut self, cb: ExitCb) {
    self.exit_cb = cb;
  }

  pub fn set_warn_cb(&mut self, cb: WarnCb) {
    self.warn_cb = cb;
  }

  /// Check if a feature is enabled.
  ///
  /// If a feature in not present in the checker, return false.
  #[inline(always)]
  pub fn check(&self, feature: &str) -> bool {
    self.features.contains(feature)
  }

  #[inline(always)]
  pub fn check_or_exit(&self, feature: &str, api_name: &str) {
    if !self.check(feature) {
      (self.exit_cb)(feature, api_name);
    }
  }

  #[inline(always)]
  pub fn check_or_exit_with_legacy_fallback(
    &self,
    feature: &str,
    api_name: &str,
  ) {
    if !self.features.contains(feature) {
      if self.legacy_unstable {
        if self.warn_on_legacy_unstable {
          (self.warn_cb)(feature);
        }
        return;
      }

      (self.exit_cb)(feature, api_name);
    }
  }

  // TODO(bartlomieju): remove this.
  pub fn enable_legacy_unstable(&mut self) {
    self.legacy_unstable = true;
  }

  // TODO(bartlomieju): remove this.
  pub fn warn_on_legacy_unstable(&mut self) {
    self.warn_on_legacy_unstable = true;
  }
}

#[cfg(test)]
mod tests {
  use std::sync::atomic::AtomicUsize;
  use std::sync::atomic::Ordering;

  use super::*;

  #[test]
  fn test_feature_checker() {
    static EXIT_COUNT: AtomicUsize = AtomicUsize::new(0);
    static WARN_COUNT: AtomicUsize = AtomicUsize::new(0);

    fn exit_cb(_feature: &str, _api_name: &str) {
      EXIT_COUNT.fetch_add(1, Ordering::Relaxed);
    }

    fn warn_cb(_feature: &str) {
      WARN_COUNT.fetch_add(1, Ordering::Relaxed);
    }

    let mut checker = FeatureChecker::default();
    checker.set_exit_cb(Box::new(exit_cb));
    checker.set_warn_cb(Box::new(warn_cb));
    checker.enable_feature("foobar");

    assert!(checker.check("foobar"));
    assert!(!checker.check("fizzbuzz"));

    checker.check_or_exit("foobar", "foo");
    assert_eq!(EXIT_COUNT.load(Ordering::Relaxed), 0);
    checker.check_or_exit("fizzbuzz", "foo");
    assert_eq!(EXIT_COUNT.load(Ordering::Relaxed), 1);

    checker.enable_legacy_unstable();
    checker.check_or_exit_with_legacy_fallback("fizzbuzz", "foo");
    assert_eq!(EXIT_COUNT.load(Ordering::Relaxed), 1);
    assert_eq!(WARN_COUNT.load(Ordering::Relaxed), 0);

    checker.warn_on_legacy_unstable();
    checker.check_or_exit_with_legacy_fallback("fizzbuzz", "foo");
    assert_eq!(EXIT_COUNT.load(Ordering::Relaxed), 1);
    assert_eq!(WARN_COUNT.load(Ordering::Relaxed), 1);
  }
}