zen-engine 1.0.0-beta.11

Business rules engine
Documentation
use std::sync::Arc;

use swc_common::errors::{DiagnosticBuilder, Emitter, Handler};
use swc_common::input::StringInput;
use swc_common::source_map::SmallPos;
use swc_common::sync::Lrc;
use swc_common::{BytePos, SourceMap, GLOBALS};
use swc_ecma_parser::{lexer::Lexer, EsSyntax, Parser, Syntax};
use swc_ts_fast_strip::{operate, Mode, Options};

pub struct TypeStripper;

struct SilentEmitter;

impl Emitter for SilentEmitter {
    fn emit(&mut self, _db: &mut DiagnosticBuilder) {}
}

impl TypeStripper {
    pub fn strip(source: &str) -> Arc<str> {
        GLOBALS.set(&Default::default(), || {
            if let Some(stripped) =
                Self::operate_with(source, Mode::StripOnly).filter(|code| Self::parses_as_js(code))
            {
                return Arc::from(stripped);
            }
            Self::operate_with(source, Mode::Transform)
                .map(Arc::from)
                .unwrap_or_else(|| Arc::from(source))
        })
    }

    fn parses_as_js(source: &str) -> bool {
        let lexer = Lexer::new(
            Syntax::Es(EsSyntax::default()),
            Default::default(),
            StringInput::new(
                source,
                BytePos::from_usize(0),
                BytePos::from_usize(source.len()),
            ),
            None,
        );
        let mut parser = Parser::new_from(lexer);
        let parsed = parser.parse_module();
        parsed.is_ok() && parser.take_errors().is_empty()
    }

    fn operate_with(source: &str, mode: Mode) -> Option<String> {
        let cm: Lrc<SourceMap> = Default::default();
        let handler = Handler::with_emitter(true, false, Box::new(SilentEmitter));
        let options = Options {
            module: Some(true),
            mode,
            ..Default::default()
        };
        operate(&cm, &handler, source.to_string(), options)
            .ok()
            .map(|output| output.code)
    }
}

#[cfg(test)]
mod tests {
    use super::TypeStripper;

    #[test]
    fn strips_type_annotations_preserving_positions() {
        let source = "export const handler = async (input: { age: number }): Promise<{ total: number }> => {\n  return { total: input.age * 2 };\n};";
        let stripped = TypeStripper::strip(source);
        assert!(!stripped.contains("number"), "{stripped}");
        assert!(
            stripped.contains("return { total: input.age * 2 };"),
            "{stripped}"
        );
        assert_eq!(source.lines().count(), stripped.lines().count());
        for (original, result) in source.lines().zip(stripped.lines()) {
            assert_eq!(original.chars().count(), result.chars().count());
        }
    }

    #[test]
    fn plain_javascript_is_untouched() {
        let source = "export const handler = async (input) => ({ total: input.age * 2 });";
        let stripped = TypeStripper::strip(source);
        assert_eq!(stripped.as_ref(), source);
    }

    #[test]
    fn interfaces_and_aliases_are_erased() {
        let source = "interface Input { age: number }\ntype Out = { total: number };\nexport const handler = (input: Input): Out => ({ total: input.age });";
        let stripped = TypeStripper::strip(source);
        assert!(!stripped.contains("interface"), "{stripped}");
        assert!(stripped.contains("({ total: input.age })"), "{stripped}");
    }

    #[test]
    fn broken_source_passes_through() {
        let source = "export const handler = (input => {";
        let stripped = TypeStripper::strip(source);
        assert_eq!(stripped.as_ref(), source);
    }

    #[test]
    fn enums_are_lowered_via_transform_mode() {
        let source = "enum Tier { Gold = 'gold', Basic = 'basic' }\nexport const handler = async (input: { vip: boolean }) => ({ tier: input.vip ? Tier.Gold : Tier.Basic });";
        let stripped = TypeStripper::strip(source);
        assert!(!stripped.contains("enum Tier"), "{stripped}");
        assert!(stripped.contains("Tier"), "{stripped}");
        assert!(!stripped.contains(": boolean"), "{stripped}");
    }
}