vize_patina 0.73.0

Patina - The quality checker for Vize code linting
Documentation
use super::{LintResult, Linter};
use crate::rules::script::{NoGetCurrentInstance, NoNextTick, NoOptionsApi, ScriptRule};
use vize_atelier_sfc::{parse_sfc, SfcDescriptor, SfcParseOptions};
use vize_carton::profile;

pub(crate) const RULE_NO_OPTIONS_API: &str = "script/no-options-api";
pub(crate) const RULE_NO_GET_CURRENT_INSTANCE: &str = "script/no-get-current-instance";
pub(crate) const RULE_NO_NEXT_TICK: &str = "script/no-next-tick";
const OPINIONATED_SCRIPT_PRESETS: &[&str] = &["opinionated", "nuxt"];

pub struct BuiltinScriptRuleMeta {
    pub name: &'static str,
    pub description: &'static str,
    pub category: &'static str,
    pub fixable: bool,
    pub default_severity: crate::Severity,
    pub presets: &'static [&'static str],
}

pub fn builtin_script_rules() -> [BuiltinScriptRuleMeta; 3] {
    let no_options_api = NoOptionsApi;
    let no_options_api_meta = no_options_api.meta();
    let no_get_current_instance = NoGetCurrentInstance;
    let no_get_current_instance_meta = no_get_current_instance.meta();
    let no_next_tick = NoNextTick;
    let no_next_tick_meta = no_next_tick.meta();

    [
        BuiltinScriptRuleMeta {
            name: no_options_api_meta.name,
            description: no_options_api_meta.description,
            category: "Vapor",
            fixable: false,
            default_severity: no_options_api_meta.default_severity,
            presets: OPINIONATED_SCRIPT_PRESETS,
        },
        BuiltinScriptRuleMeta {
            name: no_get_current_instance_meta.name,
            description: no_get_current_instance_meta.description,
            category: "Vapor",
            fixable: false,
            default_severity: no_get_current_instance_meta.default_severity,
            presets: OPINIONATED_SCRIPT_PRESETS,
        },
        BuiltinScriptRuleMeta {
            name: no_next_tick_meta.name,
            description: no_next_tick_meta.description,
            category: "Vapor",
            fixable: false,
            default_severity: no_next_tick_meta.default_severity,
            presets: OPINIONATED_SCRIPT_PRESETS,
        },
    ]
}

#[inline]
pub(crate) fn has_active_builtin_script_rules(linter: &Linter) -> bool {
    linter
        .script_rules
        .iter()
        .copied()
        .any(|rule_name| linter.is_rule_enabled(rule_name))
}

#[inline]
pub(crate) fn parse_sfc_for_lint<'a>(
    source: &'a str,
    filename: &str,
) -> Result<SfcDescriptor<'a>, vize_atelier_sfc::SfcError> {
    profile!(
        "patina.sfc.parse_for_lint",
        parse_sfc(source, sfc_parse_options(filename))
    )
}

pub(crate) fn lint_with_descriptor<'a>(
    linter: &Linter,
    filename: &str,
    descriptor: &SfcDescriptor<'a>,
) -> LintResult {
    let mut result = if let Some(template) = descriptor.template.as_ref() {
        let mut template_result = profile!(
            "patina.sfc.descriptor.template_lint",
            linter.lint_template(&template.content, filename)
        );
        let byte_offset = template.loc.start as u32;
        if byte_offset > 0 {
            for diag in &mut template_result.diagnostics {
                diag.start += byte_offset;
                diag.end += byte_offset;
                for label in &mut diag.labels {
                    label.start += byte_offset;
                    label.end += byte_offset;
                }
            }
        }
        template_result
    } else {
        LintResult {
            filename: filename.into(),
            diagnostics: Vec::new(),
            error_count: 0,
            warning_count: 0,
        }
    };

    append_builtin_script_diagnostics(linter, descriptor, &mut result);
    result
}

pub(crate) fn append_builtin_script_diagnostics<'a>(
    linter: &Linter,
    descriptor: &SfcDescriptor<'a>,
    result: &mut LintResult,
) {
    if linter.script_rules.is_empty() {
        return;
    }

    append_builtin_script_rule(
        linter,
        descriptor,
        result,
        RULE_NO_OPTIONS_API,
        "patina.script_rule.no_options_api",
        NoOptionsApi,
    );
    append_builtin_script_rule(
        linter,
        descriptor,
        result,
        RULE_NO_GET_CURRENT_INSTANCE,
        "patina.script_rule.no_get_current_instance",
        NoGetCurrentInstance,
    );
    append_builtin_script_rule(
        linter,
        descriptor,
        result,
        RULE_NO_NEXT_TICK,
        "patina.script_rule.no_next_tick",
        NoNextTick,
    );
}

fn merge_script_result(
    result: &mut LintResult,
    script_result: crate::rules::script::ScriptLintResult,
) {
    result.error_count += script_result.error_count;
    result.warning_count += script_result.warning_count;
    result.diagnostics.extend(script_result.diagnostics);
}

fn append_builtin_script_rule<'a, R: ScriptRule>(
    linter: &Linter,
    descriptor: &SfcDescriptor<'a>,
    result: &mut LintResult,
    rule_name: &str,
    profile_name: &'static str,
    rule: R,
) {
    if !linter.is_rule_enabled(rule_name) || !linter.script_rules.contains(&rule_name) {
        return;
    }

    if let Some(script) = descriptor.script.as_ref() {
        let mut lint = crate::rules::script::ScriptLintResult::default();
        profile!(
            profile_name,
            rule.check(script.content.as_ref(), script.loc.start, &mut lint)
        );
        merge_script_result(result, lint);
    }
    if let Some(script_setup) = descriptor.script_setup.as_ref() {
        let mut lint = crate::rules::script::ScriptLintResult::default();
        profile!(
            profile_name,
            rule.check(
                script_setup.content.as_ref(),
                script_setup.loc.start,
                &mut lint,
            )
        );
        merge_script_result(result, lint);
    }
}

#[inline]
fn sfc_parse_options(filename: &str) -> SfcParseOptions {
    SfcParseOptions {
        filename: filename.into(),
        ..Default::default()
    }
}