vize_l0 0.584.0

L0 - the Davinci foundation level: ids, side tables, keys, dumps, diagnostics, passes and facts
Documentation
//! Shared config model.

mod compatibility;
mod compiler;
mod defaults;
mod entries;
mod experimentals;
mod features;
mod formatter;
mod sort_imports;
use formatter::RawFormatterConfig;
pub use sort_imports::{
    ImportSortCustomGroup, ImportSortGroup, SortImportsConfig, SortImportsSetting,
};
mod global_types;
mod language_server;
mod library;
mod linter;
mod linter_execution;
mod linter_feature_flags;
mod linter_rule_options;
mod type_checker;
mod vue;

use self::{compiler::RawCompilerConfig, experimentals::RawExperimentalsConfig, vue::RawVueConfig};
use serde::{Deserialize, Serialize};

use crate::{String, dialect::VueDialect};
pub use compiler::{JsxCompat, JsxMode};
pub(crate) use entries::RawConfigEntry;
pub use entries::{
    CompilerConfigEntry, ConfigEntryFiles, ConfigEntryIgnore, LinterConfigEntry, LinterConfigPlan,
    LinterConfigPlanWithConfigRuleOptions, LinterConfigPlanWithRuleOptions, ResolvedLinterConfig,
    ResolvedLinterConfigWithConfigRuleOptions,
};
pub use experimentals::ConfigExperimentalVueFlags;
pub use features::ConfigFeatureFlags;
pub use formatter::{
    ArrowParens, AttributeSortOrder, EndOfLine, FormatterConfig, FormatterPreset, FormatterProfile,
    QuoteProps, TrailingComma,
};
pub use global_types::{GlobalTypeDeclaration, GlobalTypesConfig, RawGlobalTypesConfig};
pub use language_server::{LanguageServerConfig, LanguageServerUnstableFlags, LspConfig};
pub use library::{LibConfig, LibRegistryConfig};
pub(crate) use linter::RawLinterConfig;
pub use linter::{LintRuleSeverity, LinterConfig};
pub use linter_execution::LinterExecutionOptions;
pub use linter_feature_flags::LinterFeatureFlags;
pub use linter_rule_options::{
    ComponentNameInTemplateCasingOptions, ConfigLintRuleOptions, CustomEventNameCasing,
    CustomEventNameCasingOptions, DefineMacrosOrderOptions, DefinePropsDestructuringOptions,
    HtmlQuoteStyle, HtmlQuotesConfig, HtmlQuotesOptions, HtmlSelfClosingHtmlOptions,
    HtmlSelfClosingOptions, HtmlSelfClosingStyle, HyphenationStyle, LintRuleOptions,
    MuseaDesignToken, MuseaPreferDesignTokensOptions, MustacheInterpolationSpacingStyle,
    NoMutatingPropsOptions, NoRestrictedGlobalsOptions, NoRestrictedMembersOptions,
    PropsDestructureMode, RestrictedGlobal, RestrictedMember, SfcElementOrderGroup,
    SfcElementOrderOptions, StrictBooleanExpressionsOptions, TemplateComponentNameCasing,
    VBindStyle, VOnStyle, VSlotNamedStyle, VSlotStyle, VSlotStyleOptions, VSlotStyleValue,
    VSlotUniformStyle,
};
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,
}

/// 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>,
    /// Project identity projected by a Vite host; not a stable config field.
    #[serde(
        rename = "__vizeProjectRoot",
        deserialize_with = "deserialize_project_root"
    )]
    pub project_root: Option<String>,
    #[serde(rename = "basePath")]
    pub base_path: Option<String>,
    pub files: Option<Vec<String>>,
    pub dialect: Option<VueDialect>,
    pub formatter: RawFormatterConfig,
    pub(crate) compiler: RawCompilerConfig,
    pub(crate) compatibility: compatibility::RawCompatibilityConfig,
    pub(crate) experimentals: RawExperimentalsConfig,
    pub(crate) vue: RawVueConfig,
    pub linter: RawLinterConfig,
    #[serde(rename = "typeChecker")]
    type_checker: RawTypeCheckerConfig,
    #[serde(rename = "languageServer")]
    language_server: RawLanguageServerConfig,
    #[serde(rename = "globalTypes")]
    pub global_types: RawGlobalTypesConfig,
    pub(crate) lib: LibConfig,
    pub ignores: Option<Vec<String>>,
    pub entries: Option<Vec<RawConfigEntry>>,
    #[serde(rename = "check")]
    legacy_check: Option<LegacyCheckConfig>,
    #[serde(rename = "fmt")]
    legacy_formatter: Option<RawFormatterConfig>,
    #[serde(rename = "lsp")]
    legacy_lsp: Option<RawLanguageServerConfig>,
}

fn deserialize_project_root<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
    D: serde::Deserializer<'de>,
{
    // This host metadata used to be an unknown extension key. Preserve native
    // config compatibility by ignoring any previously authored non-string value.
    let value = serde_json::Value::deserialize(deserializer)?;
    Ok(value.as_str().map(String::from))
}

#[derive(Debug, Clone, Default, Deserialize)]
#[serde(default, rename_all = "camelCase")]
struct RawTypeCheckerConfig {
    #[serde(flatten)]
    config: TypeCheckerConfig,
    /// Experimental `tsrs` selector. Folded into the shared runtime path so
    /// downstream batch, lint, and editor consumers use one executable.
    tsrs_path: Option<String>,
    /// Editor-only Corsa bound; keep it off the public shared Rust struct so
    /// existing exhaustive `TypeCheckerConfig` literals remain source compatible.
    lsp_request_timeout_ms: Option<u64>,
    /// `None` when `typeChecker.optionsApi` is absent — defaults to enabled
    /// (matches vue-tsc). Set `false` to opt out.
    options_api: Option<bool>,
    legacy_vue2: bool,
    /// Opt-in type-checking of JSX/TSX Vue components (#1497). Default-off.
    jsx_typecheck: bool,
}

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

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

impl RawVizeConfig {
    pub(crate) fn formatter_ignore_patterns(&self) -> &[String] {
        match &self.legacy_formatter {
            Some(legacy) if self.formatter.is_unset() => &legacy.ignore_patterns,
            _ => &self.formatter.ignore_patterns,
        }
    }

    /// Authored formatter options, falling back to the legacy `fmt` alias.
    pub(crate) fn formatter_profile(&self) -> &FormatterProfile {
        match &self.legacy_formatter {
            Some(legacy) if self.formatter.is_unset() => &legacy.profile,
            _ => &self.formatter.profile,
        }
    }

    pub(crate) fn lsp_request_timeout_ms(&self) -> u64 {
        self.type_checker
            .lsp_request_timeout_ms
            .unwrap_or(60_000)
            .max(1)
    }

    /// Read the language server switches that are not stable model fields.
    ///
    /// Resolves the same `languageServer` / legacy `lsp` precedence as
    /// [`Self::into_config_and_features`] without consuming the raw config.
    pub(crate) fn language_server_unstable_flags(&self) -> LanguageServerUnstableFlags {
        let raw = match self.legacy_lsp.as_ref() {
            Some(legacy) if self.language_server.config == LanguageServerConfig::default() => {
                legacy
            }
            _ => &self.language_server,
        };
        LanguageServerUnstableFlags {
            signature_help: raw.signature_help,
        }
    }

    /// 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,
            project_root: _,
            base_path: _,
            files: _,
            dialect,
            formatter,
            compiler,
            compatibility,
            experimentals,
            vue,
            linter: _,
            type_checker: raw_type_checker,
            language_server: raw_language_server,
            global_types,
            lib: _,
            ignores: _,
            entries: _,
            legacy_check,
            legacy_formatter,
            legacy_lsp,
        } = self;

        // Default-on (matches vue-tsc); explicit `false` opts out.
        let type_checker_options_api = raw_type_checker.options_api.unwrap_or(true);
        let vue_version = vue
            .version
            .or(compiler.compatibility.vue_version)
            .or(compatibility.vue_version);
        // A Vue 2 / 2.7 dialect implies legacy template lowering. Every consumer
        // downstream of the virtual-TS generator already collapses the two into
        // `legacy_vue2 || dialect ∈ {V2, V2_7}`, but the flag itself gates
        // slot-scope and filter lowering, so without this fold `vize check` and
        // `vize lsp` both reported "Cannot find name" on pristine Vue 2.7 files
        // unless `typeChecker.legacyVue2` was *also* set (#3297). This is the
        // mirror of `LinterFeatureFlags::from_config_features`, which derives
        // the dialect from the legacy flags.
        let type_checker_legacy_vue2 = raw_type_checker.legacy_vue2
            || matches!(vue_version, Some(VueVersion::V2 | VueVersion::V2_7));
        let experimental_jsx_vapor = experimentals.jsx_vapor_enabled();
        let type_checker_jsx_typecheck = raw_type_checker.jsx_typecheck || experimental_jsx_vapor;
        let mut type_checker = raw_type_checker.config;
        if type_checker.tsgo_path.is_none() {
            type_checker.tsgo_path = raw_type_checker.tsrs_path;
        }
        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 = match legacy_formatter {
            Some(legacy) if formatter.is_unset() => legacy.config,
            _ => formatter.config,
        };

        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,
            type_checker_jsx_typecheck,
            language_server_legacy_vue2: language_server_raw.legacy_vue2,
            vue_version,
            jsx_mode: compiler
                .jsx_mode
                .or_else(|| experimental_jsx_vapor.then_some(JsxMode::Vapor)),
            jsx_compat: compiler.jsx_compat,
            experimental_vapor: experimentals.vapor_enabled(),
            experimental_jsx_vapor,
            experimental_in_tag_comments: experimentals.in_tag_comments_enabled(),
            experimental_patterned_template: experimentals.patterned_template_enabled(),
            experimental_server_script: experimentals.server_script_enabled(),
        };

        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
    }
}