vize_carton 0.204.0

Carton - The artist's toolbox for Vize compiler
Documentation
//! Shared config model.

mod compiler;
mod formatter;
mod global_types;
mod language_server;
mod linter;
mod type_checker;
mod vue;

use serde::{Deserialize, Serialize};

use compiler::RawCompilerConfig;
use vue::RawVueConfig;

use crate::String;
use crate::dialect::VueDialect;
pub use formatter::{
    ArrowParens, AttributeSortOrder, EndOfLine, FormatterConfig, QuoteProps, TrailingComma,
};
pub use global_types::{GlobalTypeDeclaration, GlobalTypesConfig, RawGlobalTypesConfig};
pub use language_server::{LanguageServerConfig, LspConfig};
#[allow(unused_imports)]
pub use linter::{LintRuleSeverity, LinterConfig};
pub use type_checker::TypeCheckerConfig;
pub use vue::{ParseVueVersionError, VueVersion};

/// Effective shared configuration.
#[derive(Debug, Clone, Default, Serialize)]
#[serde(default)]
pub struct VizeConfig {
    /// JSON Schema reference for legacy JSON editor autocompletion.
    #[serde(rename = "$schema", skip_serializing_if = "Option::is_none")]
    pub schema: Option<String>,
    /// Vue dialect profile for standalone HTML documents (`"vue"` or
    /// `"petite-vue"`). When absent, the dialect is detected structurally per
    /// document (see [`crate::dialect::standalone_html_dialect`]).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub dialect: Option<VueDialect>,
    /// Formatter settings shared by CLI and IDE formatting.
    #[serde(skip_serializing_if = "FormatterConfig::is_default")]
    pub formatter: FormatterConfig,
    /// Type checker settings shared by CLI and IDE diagnostics.
    #[serde(
        rename = "typeChecker",
        skip_serializing_if = "TypeCheckerConfig::is_default"
    )]
    pub type_checker: TypeCheckerConfig,
    /// IDE language server feature flags.
    #[serde(
        rename = "languageServer",
        skip_serializing_if = "LanguageServerConfig::is_default"
    )]
    pub language_server: LanguageServerConfig,
    /// Template global declarations.
    #[serde(
        rename = "globalTypes",
        skip_serializing_if = "GlobalTypesConfig::is_empty"
    )]
    pub global_types: GlobalTypesConfig,
}

/// Feature flags parsed from config keys that are not exposed as stable Rust
/// model fields.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct ConfigFeatureFlags {
    /// Resolve Vue 3 Options API template bindings during type checking. Opt-in
    /// and available in the standard build (not a legacy feature).
    pub type_checker_options_api: bool,
    pub type_checker_legacy_vue2: bool,
    pub language_server_legacy_vue2: Option<bool>,
    /// Dialect selected by `vue.version`; `None` when the key is absent
    /// (modern Vue 3). Validated at parse time — unknown or ambiguous values
    /// fail config loading instead of silently picking a line. Groundwork for
    /// legacy Vue support (#1392): consumers thread this into parser and
    /// transform options in follow-ups.
    pub vue_version: Option<VueVersion>,
}

/// Raw config representation with legacy aliases preserved for migration.
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(default)]
pub(crate) struct RawVizeConfig {
    #[serde(rename = "$schema")]
    pub schema: Option<String>,
    pub dialect: Option<VueDialect>,
    pub formatter: FormatterConfig,
    pub(crate) compiler: RawCompilerConfig,
    pub(crate) vue: RawVueConfig,
    pub linter: LinterConfig,
    #[serde(rename = "typeChecker")]
    type_checker: RawTypeCheckerConfig,
    #[serde(rename = "languageServer")]
    language_server: RawLanguageServerConfig,
    #[serde(rename = "globalTypes")]
    pub global_types: RawGlobalTypesConfig,
    #[serde(rename = "check")]
    legacy_check: Option<LegacyCheckConfig>,
    #[serde(rename = "fmt")]
    legacy_formatter: Option<FormatterConfig>,
    #[serde(rename = "lsp")]
    legacy_lsp: Option<RawLanguageServerConfig>,
}

#[derive(Debug, Clone, Default, Deserialize)]
#[serde(default, rename_all = "camelCase")]
struct RawTypeCheckerConfig {
    #[serde(flatten)]
    config: TypeCheckerConfig,
    options_api: bool,
    legacy_vue2: bool,
}

#[derive(Debug, Clone, Default, Deserialize)]
#[serde(default, rename_all = "camelCase")]
struct RawLanguageServerConfig {
    #[serde(flatten)]
    config: LanguageServerConfig,
    legacy_vue2: Option<bool>,
}

#[derive(Debug, Clone, Default, Deserialize)]
#[serde(default)]
struct LegacyCheckConfig {
    globals: Option<String>,
    servers: Option<usize>,
}

impl RawVizeConfig {
    /// Normalize raw config and derive auxiliary feature flags once.
    ///
    /// Legacy aliases (`check`, `fmt`, `lsp`) are folded here while the raw
    /// object is still owned. Callers that also need linter settings clone them
    /// before this conversion, which avoids a second deserialization pass.
    pub(crate) fn into_config_and_features(self) -> (VizeConfig, ConfigFeatureFlags) {
        let RawVizeConfig {
            schema,
            dialect,
            formatter,
            compiler: _,
            vue,
            linter: _,
            type_checker: raw_type_checker,
            language_server: raw_language_server,
            global_types,
            legacy_check,
            legacy_formatter,
            legacy_lsp,
        } = self;

        let type_checker_options_api = raw_type_checker.options_api;
        let type_checker_legacy_vue2 = raw_type_checker.legacy_vue2;
        let mut type_checker = raw_type_checker.config;
        if let Some(legacy_check) = legacy_check {
            if type_checker.globals_file.is_none() {
                type_checker.globals_file = legacy_check.globals;
            }
            if type_checker.servers.is_none() {
                type_checker.servers = legacy_check.servers;
            }
        }

        let formatter = if formatter == FormatterConfig::default() {
            legacy_formatter.unwrap_or(formatter)
        } else {
            formatter
        };

        let language_server_raw = if raw_language_server.config == LanguageServerConfig::default() {
            legacy_lsp.unwrap_or(raw_language_server)
        } else {
            raw_language_server
        };
        let language_server = language_server_raw.config;
        let features = ConfigFeatureFlags {
            type_checker_options_api,
            type_checker_legacy_vue2,
            language_server_legacy_vue2: language_server_raw.legacy_vue2,
            vue_version: vue.version,
        };

        let config = VizeConfig {
            schema,
            dialect,
            formatter,
            type_checker,
            language_server,
            global_types: global_types.into(),
        };

        (config, features)
    }
}

impl From<RawVizeConfig> for VizeConfig {
    fn from(raw: RawVizeConfig) -> Self {
        raw.into_config_and_features().0
    }
}