perl-pragma 0.15.2

Perl pragma extraction and analysis primitives
Documentation
use crate::{
    PragmaState, add_disabled_warning_category, apply_builtin_imports, apply_feature_state,
    conditional_pragma_target, enable_effective_version_semantics, normalized_pragma_token,
    parse_perl_version, pragma_arg_items, remove_builtin_imports,
};
use std::ops::Range;

pub(super) fn apply_use_directive(
    range: Range<usize>,
    module: &str,
    args: &[String],
    state: &mut PragmaState,
    ranges: &mut Vec<(Range<usize>, PragmaState)>,
) {
    if apply_conditional_use(module, args, range.clone(), state, ranges) {
        return;
    }

    match module {
        "strict" => {
            set_strict_categories(state, args, true);
            push_state(range, state, ranges);
        }
        "warnings" => {
            apply_use_warnings(range, args, state, ranges);
        }
        "utf8" => {
            state.utf8 = true;
            push_state(range, state, ranges);
        }
        "encoding" => {
            state.encoding = first_normalized_arg(args);
            push_state(range, state, ranges);
        }
        "locale" => {
            state.locale = true;
            state.locale_scope = first_normalized_arg(args);
            push_state(range, state, ranges);
        }
        "feature" => {
            if apply_feature_state(state, args, true) {
                push_state(range, state, ranges);
            }
        }
        "builtin" => {
            apply_builtin_imports(state, args);
            push_state(range, state, ranges);
        }
        _ => {
            if let Some(version) = parse_perl_version(module) {
                enable_effective_version_semantics(state, version);
                push_state(range, state, ranges);
            }
        }
    }
}

pub(super) fn apply_no_directive(
    range: Range<usize>,
    module: &str,
    args: &[String],
    state: &mut PragmaState,
    ranges: &mut Vec<(Range<usize>, PragmaState)>,
) {
    if apply_conditional_no(module, args, range.clone(), state, ranges) {
        return;
    }

    match module {
        "strict" => {
            set_strict_categories(state, args, false);
            push_state(range, state, ranges);
        }
        "warnings" => {
            apply_no_warnings(range, args, state, ranges);
        }
        "utf8" => {
            state.utf8 = false;
            push_state(range, state, ranges);
        }
        "encoding" => {
            state.encoding = None;
            push_state(range, state, ranges);
        }
        "locale" => {
            state.locale = false;
            state.locale_scope = None;
            push_state(range, state, ranges);
        }
        "feature" => {
            if apply_feature_state(state, args, false) {
                push_state(range, state, ranges);
            }
        }
        "builtin" => {
            remove_builtin_imports(state, args);
            push_state(range, state, ranges);
        }
        _ => {}
    }
}

fn apply_conditional_use(
    module: &str,
    args: &[String],
    range: Range<usize>,
    state: &mut PragmaState,
    ranges: &mut Vec<(Range<usize>, PragmaState)>,
) -> bool {
    if !matches!(module, "if" | "unless") {
        return false;
    }

    if let Some((target, target_args)) = conditional_pragma_target(args) {
        apply_conditional_use_target(range, target, target_args, state, ranges);
    }
    true
}

fn apply_conditional_no(
    module: &str,
    args: &[String],
    range: Range<usize>,
    state: &mut PragmaState,
    ranges: &mut Vec<(Range<usize>, PragmaState)>,
) -> bool {
    if !matches!(module, "if" | "unless") {
        return false;
    }

    if let Some((target, target_args)) = conditional_pragma_target(args) {
        apply_conditional_no_target(range, target, target_args, state, ranges);
    }
    true
}

fn apply_conditional_use_target(
    range: Range<usize>,
    module: &str,
    args: &[String],
    state: &mut PragmaState,
    ranges: &mut Vec<(Range<usize>, PragmaState)>,
) {
    match module {
        "strict" => set_strict_categories(state, args, true),
        "warnings" => {
            enable_warnings_categories(args, state);
        }
        "utf8" => state.utf8 = true,
        "encoding" => state.encoding = first_normalized_arg(args),
        "locale" => {
            state.locale = true;
            state.locale_scope = first_normalized_arg(args);
        }
        "feature" => {
            if !apply_feature_state(state, args, true) {
                return;
            }
        }
        "builtin" => apply_builtin_imports(state, args),
        _ => {
            if let Some(version) = parse_perl_version(module) {
                enable_effective_version_semantics(state, version);
            } else {
                return;
            }
        }
    }
    push_state(range, state, ranges);
}

fn apply_conditional_no_target(
    range: Range<usize>,
    module: &str,
    args: &[String],
    state: &mut PragmaState,
    ranges: &mut Vec<(Range<usize>, PragmaState)>,
) {
    match module {
        "strict" => set_strict_categories(state, args, false),
        "warnings" => disable_warnings_categories(args, state),
        "utf8" => state.utf8 = false,
        "encoding" => state.encoding = None,
        "locale" => {
            state.locale = false;
            state.locale_scope = None;
        }
        "feature" => {
            if !apply_feature_state(state, args, false) {
                return;
            }
        }
        "builtin" => remove_builtin_imports(state, args),
        _ => return,
    }
    push_state(range, state, ranges);
}

fn set_strict_categories(state: &mut PragmaState, args: &[String], enabled: bool) {
    if args.is_empty() {
        state.strict_vars = enabled;
        state.strict_subs = enabled;
        state.strict_refs = enabled;
        return;
    }

    for arg in args {
        for item in pragma_arg_items(arg) {
            match item.as_str() {
                "vars" => state.strict_vars = enabled,
                "subs" => state.strict_subs = enabled,
                "refs" => state.strict_refs = enabled,
                _ => {}
            }
        }
    }
}

fn apply_use_warnings(
    range: Range<usize>,
    args: &[String],
    state: &mut PragmaState,
    ranges: &mut Vec<(Range<usize>, PragmaState)>,
) {
    enable_warnings_categories(args, state);
    push_state(range, state, ranges);
}

fn enable_warnings_categories(args: &[String], state: &mut PragmaState) {
    state.warnings = true;

    if args.is_empty() {
        state.disabled_warning_categories.clear();
        return;
    }

    for arg in args {
        for category in pragma_arg_items(arg) {
            state.disabled_warning_categories.retain(|disabled| disabled != &category);
        }
    }
}

fn apply_no_warnings(
    range: Range<usize>,
    args: &[String],
    state: &mut PragmaState,
    ranges: &mut Vec<(Range<usize>, PragmaState)>,
) {
    let warnings_before = state.warnings;
    let had_disabled_before = !state.disabled_warning_categories.is_empty();
    let before = state.disabled_warning_categories.len();
    disable_warnings_categories(args, state);

    let changed = if args.is_empty() {
        warnings_before || had_disabled_before
    } else {
        state.disabled_warning_categories.len() != before
    };
    if changed {
        push_state(range, state, ranges);
    }
}

fn disable_warnings_categories(args: &[String], state: &mut PragmaState) {
    if args.is_empty() {
        state.warnings = false;
        state.disabled_warning_categories.clear();
        return;
    }

    for arg in args {
        for category in pragma_arg_items(arg) {
            add_disabled_warning_category(state, &category);
        }
    }
}

fn first_normalized_arg(args: &[String]) -> Option<String> {
    args.first().map(|arg| normalized_pragma_token(arg).to_string())
}

fn push_state(
    range: Range<usize>,
    state: &PragmaState,
    ranges: &mut Vec<(Range<usize>, PragmaState)>,
) {
    ranges.push((range, state.clone()));
}