#[cfg(test)]
use std::collections::BTreeSet;
use super::diagnostic_suppressions::apply_omena_query_style_diagnostic_suppressions;
use super::diagnostic_suppressions::parse_omena_query_style_strictness_level;
use super::diagnostic_suppressions::report_omena_query_style_diagnostic_suppressions;
use super::*;
mod cascade_runtime;
mod cross_file_scc;
mod css_modules;
mod external_sif;
mod render;
mod replica_ensemble;
mod sass;
mod sass_builtins;
mod sass_resolution;
mod sass_symbols;
mod shared;
mod single_file;
mod source_usage;
mod substrate;
pub(in crate::style) use cascade_runtime::collect_omena_query_inline_style_runtime_overrides_by_style;
#[cfg(feature = "hypergraph-ifds")]
pub(in crate::style) use cross_file_scc::collect_omena_query_unified_cross_file_scc_report_shared;
pub(in crate::style) use source_usage::collect_omena_query_unused_selector_shared;
pub(in crate::style) use substrate::OmenaQueryWorkspaceSharedPassProductsV0;
mod types;
use cascade_runtime::{
attach_omena_query_module_graph_property_value_narrowing_for_workspace,
attach_omena_query_runtime_state_inline_overrides_for_workspace,
};
use cross_file_scc::summarize_omena_query_unified_cross_file_scc_diagnostics_for_workspace;
use css_modules::summarize_omena_query_css_modules_resolution_style_diagnostics_from_entries;
pub use css_modules::{
summarize_omena_query_css_modules_local_composes_style_diagnostics,
summarize_omena_query_css_modules_resolution_style_diagnostics,
};
#[cfg(test)]
use external_sif::omena_query_external_sif_canonical_urls_match;
pub(super) use external_sif::{
OmenaQueryExternalSifResolutionContext, promote_sif_backed_external_edges,
};
use external_sif::{
collect_omena_query_external_top_any_sass_symbol_ranges,
summarize_omena_query_external_sif_boundary_diagnostics,
target_has_auto_external_boundary_edges,
};
use replica_ensemble::summarize_omena_query_replica_ensemble_inconsistency_diagnostics_for_workspace;
#[cfg(test)]
use sass::sass_import_is_plain_css;
pub(super) use sass::sass_module_source_is_workspace_local;
pub use sass::{
summarize_omena_query_missing_extend_target_diagnostics,
summarize_omena_query_missing_sass_symbol_diagnostics,
summarize_omena_query_missing_sass_symbol_diagnostics_for_workspace,
summarize_omena_query_sass_import_deprecation_hints,
summarize_omena_query_target_unresolved_sass_import_diagnostics_for_workspace_paths,
};
use sass::{
summarize_omena_query_missing_extend_target_diagnostics_for_workspace,
summarize_omena_query_missing_sass_symbol_diagnostics_for_workspace_with_sifs_from_substrate,
summarize_omena_query_sass_use_cycle_diagnostics_for_workspace,
summarize_omena_query_unresolved_sass_import_diagnostics_for_workspace,
};
#[cfg(test)]
use sass_builtins::{sass_builtin_module_function_names, sass_builtin_module_mixin_names};
pub use sass_resolution::summarize_omena_query_sass_module_resolution_identity_diagnostics_for_workspace;
pub(in crate::style) use sass_resolution::summarize_omena_query_sass_module_resolution_identity_diagnostics_for_workspace_from_resolution;
pub(crate) use sass_symbols::SassSymbolKey;
#[cfg(test)]
pub(crate) use sass_symbols::collect_omena_query_visible_sass_symbol_keys_for_workspace_file;
pub(super) use sass_symbols::collect_visible_sass_symbol_keys;
pub use single_file::{
summarize_omena_query_cascade_aware_style_diagnostics,
summarize_omena_query_cascade_aware_style_diagnostics_with_deep_analysis,
summarize_omena_query_missing_custom_property_diagnostics,
summarize_omena_query_missing_keyframes_diagnostics,
};
use source_usage::summarize_omena_query_unused_selector_style_diagnostics_with_path_mappings_from_entries;
pub use source_usage::{
OmenaQueryCssModuleExportUsageStatusV0, OmenaQueryCssModuleExportUsageV0,
OmenaQueryCssModulesExportUsageReportV0, OmenaQueryCssModulesUnusedExportDiagnosticV0,
OmenaQueryCssModulesUnusedExportSkipReasonCountV0,
OmenaQueryCssModulesUnusedExportSkipReasonV0, summarize_omena_query_css_modules_export_usage,
};
pub use source_usage::{
summarize_omena_query_unused_selector_style_diagnostics,
summarize_omena_query_unused_selector_style_diagnostics_with_path_mappings,
};
pub(super) use substrate::collect_sass_module_graph_reachable_style_paths;
pub(in crate::style) use substrate::{
OmenaQueryWorkspaceDiagnosticsSubstrateV0, collect_omena_query_workspace_diagnostics_substrate,
};
#[cfg(feature = "salsa-memo")]
pub(in crate::style) use substrate::{
collect_omena_query_workspace_diagnostics_substrate_from_committed_graph,
collect_omena_query_workspace_diagnostics_substrate_from_entries,
};
pub use types::OmenaQueryExternalModuleModeV0;
pub fn summarize_omena_query_style_diagnostics_for_file(
style_uri: &str,
source: &str,
candidates: &[OmenaQueryStyleHoverCandidateV0],
) -> OmenaQueryStyleDiagnosticsForFileV0 {
summarize_omena_query_style_diagnostics_for_file_with_deep_analysis(
style_uri, source, candidates, false,
)
}
pub fn summarize_omena_query_style_diagnostics_for_file_with_deep_analysis(
style_uri: &str,
source: &str,
candidates: &[OmenaQueryStyleHoverCandidateV0],
deep_analysis: bool,
) -> OmenaQueryStyleDiagnosticsForFileV0 {
let mut diagnostics =
summarize_omena_query_missing_custom_property_diagnostics(style_uri, source, candidates);
diagnostics.extend(
summarize_omena_query_cascade_aware_style_diagnostics_with_deep_analysis(
style_uri,
source,
candidates,
deep_analysis,
),
);
diagnostics.extend(summarize_omena_query_missing_keyframes_diagnostics(
style_uri, source,
));
diagnostics.extend(summarize_omena_query_sass_import_deprecation_hints(
style_uri, source,
));
diagnostics.extend(summarize_omena_query_missing_sass_symbol_diagnostics(
style_uri, source,
));
diagnostics.extend(summarize_omena_query_missing_extend_target_diagnostics(
style_uri, source,
));
apply_omena_query_checker_product_gate_to_style_diagnostics(&mut diagnostics);
let mut summary = OmenaQueryStyleDiagnosticsForFileV0 {
schema_version: "0",
product: "omena-query.diagnostics-for-file",
file_uri: style_uri.to_string(),
file_kind: "style",
diagnostic_count: diagnostics.len(),
diagnostics,
ready_surfaces: vec![
"missingCustomPropertyDiagnostics",
"cascadeAwareDiagnostics",
"missingKeyframesDiagnostics",
"sassImportDeprecationHints",
"missingSassSymbolDiagnostics",
"missingExtendTargetDiagnostics",
"checkerProductDiagnosticGate",
"runtimeStateScenarioEvidence",
],
suppression_summary: None,
};
apply_omena_query_style_diagnostic_suppressions(source, &mut summary);
summary
}
pub fn summarize_omena_query_style_diagnostics_for_file_with_local_composes(
style_uri: &str,
source: &str,
candidates: &[OmenaQueryStyleHoverCandidateV0],
) -> OmenaQueryStyleDiagnosticsForFileV0 {
summarize_omena_query_style_diagnostics_for_file_with_local_composes_and_deep_analysis(
style_uri, source, candidates, false,
)
}
pub fn summarize_omena_query_style_diagnostics_for_file_with_local_composes_and_deep_analysis(
style_uri: &str,
source: &str,
candidates: &[OmenaQueryStyleHoverCandidateV0],
deep_analysis: bool,
) -> OmenaQueryStyleDiagnosticsForFileV0 {
let mut summary = summarize_omena_query_style_diagnostics_for_file_with_deep_analysis(
style_uri,
source,
candidates,
deep_analysis,
);
let mut local_composes =
summarize_omena_query_css_modules_local_composes_style_diagnostics(style_uri, source);
apply_omena_query_checker_product_gate_to_style_diagnostics(&mut local_composes);
if !local_composes.is_empty() {
summary.diagnostics.extend(local_composes);
push_omena_query_ready_surface(
&mut summary.ready_surfaces,
"cssModulesComposesResolutionDiagnostics",
);
apply_omena_query_style_diagnostic_suppressions(source, &mut summary);
summary.diagnostic_count = summary.diagnostics.len();
}
summary
}
pub fn summarize_omena_query_style_diagnostics_for_workspace_file(
target_style_path: &str,
style_sources: &[OmenaQueryStyleSourceInputV0],
source_documents: &[OmenaQuerySourceDocumentInputV0],
package_manifests: &[OmenaQueryStylePackageManifestV0],
classname_transform: Option<&str>,
) -> Option<OmenaQueryStyleDiagnosticsForFileV0> {
summarize_omena_query_style_diagnostics_for_workspace_file_with_external_mode(
target_style_path,
style_sources,
source_documents,
package_manifests,
classname_transform,
OmenaQueryExternalModuleModeV0::Ignored,
)
}
pub fn summarize_omena_query_style_diagnostics_for_workspace_file_with_external_mode(
target_style_path: &str,
style_sources: &[OmenaQueryStyleSourceInputV0],
source_documents: &[OmenaQuerySourceDocumentInputV0],
package_manifests: &[OmenaQueryStylePackageManifestV0],
classname_transform: Option<&str>,
external_mode: OmenaQueryExternalModuleModeV0,
) -> Option<OmenaQueryStyleDiagnosticsForFileV0> {
summarize_omena_query_style_diagnostics_for_workspace_file_with_external_mode_and_sifs(
target_style_path,
style_sources,
source_documents,
package_manifests,
classname_transform,
external_mode,
&[],
)
}
pub fn summarize_omena_query_style_diagnostics_for_workspace_file_with_external_mode_and_sifs(
target_style_path: &str,
style_sources: &[OmenaQueryStyleSourceInputV0],
source_documents: &[OmenaQuerySourceDocumentInputV0],
package_manifests: &[OmenaQueryStylePackageManifestV0],
classname_transform: Option<&str>,
external_mode: OmenaQueryExternalModuleModeV0,
external_sifs: &[OmenaQueryExternalSifInputV0],
) -> Option<OmenaQueryStyleDiagnosticsForFileV0> {
summarize_omena_query_style_diagnostics_for_workspace_file_with_external_mode_and_sifs_and_suppression_mode(
target_style_path,
style_sources,
source_documents,
package_manifests,
classname_transform,
external_mode,
external_sifs,
OmenaQueryDiagnosticSuppressionModeV0::Apply,
)
}
#[allow(clippy::too_many_arguments)]
pub fn summarize_omena_query_style_diagnostics_for_workspace_file_with_external_mode_and_sifs_and_suppression_mode(
target_style_path: &str,
style_sources: &[OmenaQueryStyleSourceInputV0],
source_documents: &[OmenaQuerySourceDocumentInputV0],
package_manifests: &[OmenaQueryStylePackageManifestV0],
classname_transform: Option<&str>,
external_mode: OmenaQueryExternalModuleModeV0,
external_sifs: &[OmenaQueryExternalSifInputV0],
suppression_mode: OmenaQueryDiagnosticSuppressionModeV0,
) -> Option<OmenaQueryStyleDiagnosticsForFileV0> {
summarize_omena_query_style_diagnostics_for_workspace_file_with_external_mode_and_sifs_and_resolution_inputs_and_suppression_mode(
target_style_path,
style_sources,
source_documents,
package_manifests,
classname_transform,
external_mode,
external_sifs,
&OmenaQueryStyleResolutionInputsV0 {
package_manifests: package_manifests.to_vec(),
..Default::default()
},
suppression_mode,
)
}
#[allow(clippy::too_many_arguments)]
pub fn summarize_omena_query_style_diagnostics_for_workspace_file_with_external_mode_and_sifs_and_resolution_inputs(
target_style_path: &str,
style_sources: &[OmenaQueryStyleSourceInputV0],
source_documents: &[OmenaQuerySourceDocumentInputV0],
package_manifests: &[OmenaQueryStylePackageManifestV0],
classname_transform: Option<&str>,
external_mode: OmenaQueryExternalModuleModeV0,
external_sifs: &[OmenaQueryExternalSifInputV0],
resolution_inputs: &OmenaQueryStyleResolutionInputsV0,
) -> Option<OmenaQueryStyleDiagnosticsForFileV0> {
summarize_omena_query_style_diagnostics_for_workspace_file_with_external_mode_and_sifs_and_resolution_inputs_and_suppression_mode(
target_style_path,
style_sources,
source_documents,
package_manifests,
classname_transform,
external_mode,
external_sifs,
resolution_inputs,
OmenaQueryDiagnosticSuppressionModeV0::Apply,
)
}
#[allow(clippy::too_many_arguments)]
pub fn summarize_omena_query_style_diagnostics_for_workspace_file_with_external_mode_and_sifs_and_resolution_inputs_and_suppression_mode(
target_style_path: &str,
style_sources: &[OmenaQueryStyleSourceInputV0],
source_documents: &[OmenaQuerySourceDocumentInputV0],
package_manifests: &[OmenaQueryStylePackageManifestV0],
classname_transform: Option<&str>,
external_mode: OmenaQueryExternalModuleModeV0,
external_sifs: &[OmenaQueryExternalSifInputV0],
resolution_inputs: &OmenaQueryStyleResolutionInputsV0,
suppression_mode: OmenaQueryDiagnosticSuppressionModeV0,
) -> Option<OmenaQueryStyleDiagnosticsForFileV0> {
let substrate = collect_omena_query_workspace_diagnostics_substrate(
style_sources,
package_manifests,
external_sifs,
resolution_inputs.bundler_path_mappings.as_slice(),
resolution_inputs.tsconfig_path_mappings.as_slice(),
);
summarize_omena_query_style_diagnostics_for_workspace_file_with_external_mode_and_sifs_and_resolution_inputs_and_suppression_mode_with_substrate(
target_style_path,
style_sources,
source_documents,
package_manifests,
classname_transform,
external_mode,
external_sifs,
resolution_inputs,
suppression_mode,
&substrate,
None,
)
}
#[allow(clippy::too_many_arguments)]
pub(in crate::style) fn summarize_omena_query_style_diagnostics_for_workspace_file_with_external_mode_and_sifs_and_resolution_inputs_and_suppression_mode_with_substrate(
target_style_path: &str,
style_sources: &[OmenaQueryStyleSourceInputV0],
source_documents: &[OmenaQuerySourceDocumentInputV0],
package_manifests: &[OmenaQueryStylePackageManifestV0],
classname_transform: Option<&str>,
external_mode: OmenaQueryExternalModuleModeV0,
external_sifs: &[OmenaQueryExternalSifInputV0],
resolution_inputs: &OmenaQueryStyleResolutionInputsV0,
suppression_mode: OmenaQueryDiagnosticSuppressionModeV0,
substrate: &OmenaQueryWorkspaceDiagnosticsSubstrateV0,
resolver_identity_index: Option<&OmenaResolverStyleModuleConfirmationIdentityIndexV0>,
) -> Option<OmenaQueryStyleDiagnosticsForFileV0> {
summarize_omena_query_style_diagnostics_for_workspace_file_with_external_mode_and_sifs_and_resolution_inputs_and_suppression_mode_with_substrate_and_shared(
target_style_path,
style_sources,
source_documents,
package_manifests,
classname_transform,
external_mode,
external_sifs,
resolution_inputs,
suppression_mode,
substrate,
resolver_identity_index,
None,
)
}
#[allow(clippy::too_many_arguments)]
pub(in crate::style) fn summarize_omena_query_style_diagnostics_for_workspace_file_with_external_mode_and_sifs_and_resolution_inputs_and_suppression_mode_with_substrate_and_shared(
target_style_path: &str,
style_sources: &[OmenaQueryStyleSourceInputV0],
source_documents: &[OmenaQuerySourceDocumentInputV0],
package_manifests: &[OmenaQueryStylePackageManifestV0],
classname_transform: Option<&str>,
external_mode: OmenaQueryExternalModuleModeV0,
external_sifs: &[OmenaQueryExternalSifInputV0],
resolution_inputs: &OmenaQueryStyleResolutionInputsV0,
suppression_mode: OmenaQueryDiagnosticSuppressionModeV0,
substrate: &OmenaQueryWorkspaceDiagnosticsSubstrateV0,
resolver_identity_index: Option<&OmenaResolverStyleModuleConfirmationIdentityIndexV0>,
shared_passes: Option<&substrate::OmenaQueryWorkspaceSharedPassProductsV0>,
) -> Option<OmenaQueryStyleDiagnosticsForFileV0> {
let target = style_sources
.iter()
.find(|source| source.style_path == target_style_path)?;
let candidates =
summarize_omena_query_style_hover_candidates(target_style_path, &target.style_source)?;
let mut summary = summarize_omena_query_style_diagnostics_for_file(
target_style_path,
&target.style_source,
candidates.candidates.as_slice(),
);
summary
.diagnostics
.retain(|diagnostic| diagnostic.code != "missingSassSymbol");
summary
.diagnostics
.retain(|diagnostic| diagnostic.code != "missingExtendTarget");
summary.diagnostics.extend(
summarize_omena_query_missing_extend_target_diagnostics_for_workspace(
target_style_path,
style_sources,
substrate,
),
);
summary.diagnostics.extend(
summarize_omena_query_missing_sass_symbol_diagnostics_for_workspace_with_sifs_from_substrate(
target_style_path,
style_sources,
package_manifests,
external_sifs,
resolution_inputs.bundler_path_mappings.as_slice(),
resolution_inputs.tsconfig_path_mappings.as_slice(),
&substrate.style_fact_entries,
&substrate.sass_resolution_with_external_sifs,
),
);
summary.diagnostics.extend(
summarize_omena_query_css_modules_resolution_style_diagnostics_from_entries(
target_style_path,
&target.style_source,
&substrate.style_fact_entries,
package_manifests,
resolution_inputs,
resolver_identity_index,
),
);
summary.diagnostics.extend(
summarize_omena_query_sass_use_cycle_diagnostics_for_workspace(
target_style_path,
style_sources,
substrate,
),
);
#[cfg(feature = "hypergraph-ifds")]
match shared_passes.and_then(|shared| shared.cross_file_scc_report.as_ref()) {
Some(report) => summary.diagnostics.extend(
cross_file_scc::summarize_omena_query_unified_cross_file_scc_diagnostics_from_report(
target_style_path,
&target.style_source,
report,
),
),
None => summary.diagnostics.extend(
summarize_omena_query_unified_cross_file_scc_diagnostics_for_workspace(
target_style_path,
&target.style_source,
style_sources,
source_documents,
package_manifests,
resolution_inputs,
substrate,
),
),
}
#[cfg(not(feature = "hypergraph-ifds"))]
summary.diagnostics.extend(
summarize_omena_query_unified_cross_file_scc_diagnostics_for_workspace(
target_style_path,
&target.style_source,
style_sources,
source_documents,
package_manifests,
resolution_inputs,
substrate,
),
);
summary.diagnostics.extend(
summarize_omena_query_unresolved_sass_import_diagnostics_for_workspace(
target_style_path,
style_sources,
substrate,
),
);
summary.diagnostics.extend(
summarize_omena_query_sass_module_resolution_identity_diagnostics_for_workspace_from_resolution(
target_style_path,
style_sources,
&substrate.sass_resolution,
),
);
match shared_passes {
Some(shared) => {
if let Some(unused_selector) = shared.unused_selector.as_ref() {
summary.diagnostics.extend(
source_usage::summarize_omena_query_unused_selector_style_diagnostics_with_shared(
target_style_path,
&target.style_source,
unused_selector,
),
);
}
}
None => summary.diagnostics.extend(
summarize_omena_query_unused_selector_style_diagnostics_with_path_mappings_from_entries(
target_style_path,
&target.style_source,
&substrate.style_fact_entries,
source_documents,
package_manifests,
classname_transform,
resolution_inputs.bundler_path_mappings.as_slice(),
resolution_inputs.tsconfig_path_mappings.as_slice(),
resolution_inputs.disk_style_path_identities.as_slice(),
resolver_identity_index,
),
),
}
summary.diagnostics.extend(
summarize_omena_query_replica_ensemble_inconsistency_diagnostics_for_workspace(
target_style_path,
style_sources,
substrate,
),
);
attach_omena_query_module_graph_property_value_narrowing_for_workspace(
target_style_path,
&mut summary,
style_sources,
substrate,
);
summary.diagnostic_count = summary.diagnostics.len();
push_omena_query_ready_surface(
&mut summary.ready_surfaces,
"cssModulesComposesResolutionDiagnostics",
);
push_omena_query_ready_surface(
&mut summary.ready_surfaces,
"cssModulesValueResolutionDiagnostics",
);
push_omena_query_ready_surface(&mut summary.ready_surfaces, "unusedSelectorDiagnostics");
push_omena_query_ready_surface(&mut summary.ready_surfaces, "sassUseCycleDiagnostics");
push_omena_query_ready_surface(&mut summary.ready_surfaces, "crossFileSccDiagnostics");
push_omena_query_ready_surface(
&mut summary.ready_surfaces,
"unresolvedSassImportDiagnostics",
);
push_omena_query_ready_surface(
&mut summary.ready_surfaces,
"sassModuleResolutionIdentityDiagnostics",
);
push_omena_query_ready_surface(
&mut summary.ready_surfaces,
"missingExtendTargetDiagnostics",
);
push_omena_query_ready_surface(
&mut summary.ready_surfaces,
"graphAwareSassSymbolDiagnostics",
);
push_omena_query_ready_surface(
&mut summary.ready_surfaces,
"crossFileReplicaEnsembleDiagnostics",
);
push_omena_query_ready_surface(
&mut summary.ready_surfaces,
"crossFileReplicaEnsembleHintScope",
);
push_omena_query_ready_surface(
&mut summary.ready_surfaces,
"moduleGraphPropertyValueNarrowing",
);
let external_sif_context = OmenaQueryExternalSifResolutionContext {
package_manifests,
bundler_path_mappings: resolution_inputs.bundler_path_mappings.as_slice(),
tsconfig_path_mappings: resolution_inputs.tsconfig_path_mappings.as_slice(),
external_sifs,
};
let external_boundary_enabled = match external_mode {
OmenaQueryExternalModuleModeV0::Ignored => false,
OmenaQueryExternalModuleModeV0::Sif => true,
OmenaQueryExternalModuleModeV0::Auto => target_has_auto_external_boundary_edges(
target_style_path,
external_sif_context,
substrate,
),
};
if external_boundary_enabled {
let strictness = parse_omena_query_style_strictness_level(&target.style_source);
let top_any_external_symbol_ranges =
collect_omena_query_external_top_any_sass_symbol_ranges(
target_style_path,
style_sources,
package_manifests,
resolution_inputs.bundler_path_mappings.as_slice(),
resolution_inputs.tsconfig_path_mappings.as_slice(),
external_sifs,
substrate,
);
if strictness.suppresses_top_any_external_symbols() {
summary.diagnostics.retain(|diagnostic| {
diagnostic.code != "missingSassSymbol"
|| !top_any_external_symbol_ranges.contains(&diagnostic.range)
});
} else {
for diagnostic in summary.diagnostics.iter_mut() {
if diagnostic.code == "missingSassSymbol"
&& top_any_external_symbol_ranges.contains(&diagnostic.range)
{
diagnostic.severity = "error";
}
}
}
if strictness.emits_external_boundary_diagnostics() {
summary
.diagnostics
.extend(summarize_omena_query_external_sif_boundary_diagnostics(
target_style_path,
style_sources,
package_manifests,
resolution_inputs.bundler_path_mappings.as_slice(),
resolution_inputs.tsconfig_path_mappings.as_slice(),
external_sifs,
strictness,
substrate,
));
}
push_omena_query_ready_surface(
&mut summary.ready_surfaces,
"externalSifBoundaryDiagnostics",
);
push_omena_query_ready_surface(&mut summary.ready_surfaces, "strictnessSigilGating");
}
match shared_passes.and_then(|shared| shared.inline_style_overrides_by_style.as_ref()) {
Some(overrides_by_style) => {
cascade_runtime::attach_omena_query_runtime_state_inline_overrides_with_shared(
target_style_path,
&mut summary,
overrides_by_style,
)
}
None => attach_omena_query_runtime_state_inline_overrides_for_workspace(
target_style_path,
&mut summary,
style_sources,
source_documents,
resolution_inputs,
resolver_identity_index,
),
}
push_omena_query_ready_surface(
&mut summary.ready_surfaces,
"runtimeStateInlineStyleOverrides",
);
apply_omena_query_checker_product_gate_to_style_diagnostics(&mut summary.diagnostics);
push_omena_query_ready_surface(&mut summary.ready_surfaces, "checkerProductDiagnosticGate");
match suppression_mode {
OmenaQueryDiagnosticSuppressionModeV0::Apply => {
apply_omena_query_style_diagnostic_suppressions(&target.style_source, &mut summary);
}
OmenaQueryDiagnosticSuppressionModeV0::ReportOnly => {
report_omena_query_style_diagnostic_suppressions(&target.style_source, &mut summary);
}
}
summary.diagnostic_count = summary.diagnostics.len();
Some(summary)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn external_sif_canonical_url_match_accepts_raw_path_and_file_uri() {
assert!(omena_query_external_sif_canonical_urls_match(
"/tmp/tokens.scss",
"file:///tmp/tokens.scss",
));
}
#[test]
fn sass_import_is_plain_css_classifies_css_forms() {
assert!(sass_import_is_plain_css("url(theme.css)"));
assert!(sass_import_is_plain_css("url(theme.scss)")); assert!(sass_import_is_plain_css("vendor.css"));
assert!(sass_import_is_plain_css("VENDOR.CSS")); assert!(sass_import_is_plain_css("//cdn.example/x.css"));
assert!(sass_import_is_plain_css("https://x.com/y.css"));
assert!(sass_import_is_plain_css("http://x.com/y")); }
#[test]
fn sass_import_is_plain_css_keeps_partials_flaggable() {
assert!(!sass_import_is_plain_css("partial"));
assert!(!sass_import_is_plain_css("./legacy"));
assert!(!sass_import_is_plain_css("foundation/buttons"));
assert!(!sass_import_is_plain_css("legacy")); }
#[test]
fn media_qualified_import_is_not_deprecated() {
let screen = summarize_omena_query_sass_import_deprecation_hints(
"theme.scss",
"@import \"foo\" screen;",
);
assert!(
screen.is_empty(),
"media-qualified (Ident) @import must NOT warn deprecatedSassImport, got {screen:?}"
);
let feature = summarize_omena_query_sass_import_deprecation_hints(
"theme.scss",
"@import \"foo\" (min-width: 100px);",
);
assert!(
feature.is_empty(),
"media-feature-qualified @import must NOT warn deprecatedSassImport, got {feature:?}"
);
}
#[test]
fn bare_partial_import_still_deprecated() {
let diagnostics =
summarize_omena_query_sass_import_deprecation_hints("theme.scss", "@import 'partial';");
assert_eq!(
diagnostics.len(),
1,
"bare Sass partial @import must still warn, got {diagnostics:?}"
);
assert_eq!(diagnostics[0].code, "deprecatedSassImport");
}
#[test]
fn media_qualified_comma_peer_classifies_per_target() {
let diagnostics = summarize_omena_query_sass_import_deprecation_hints(
"theme.scss",
"@import \"a\", \"b\" screen;",
);
assert_eq!(
diagnostics.len(),
1,
"exactly the bare partial peer must warn, got {diagnostics:?}"
);
}
#[test]
fn sass_meta_allowlist_matches_pinned_1_77_surface() {
let pinned_functions: BTreeSet<&str> = [
"accepts-content",
"calc-args",
"calc-name",
"call",
"content-exists",
"feature-exists",
"function-exists",
"get-function",
"get-mixin",
"global-variable-exists",
"inspect",
"keywords",
"mixin-exists",
"module-functions",
"module-mixins",
"module-variables",
"type-of",
"variable-exists",
]
.into_iter()
.collect();
let pinned_mixins: BTreeSet<&str> = ["apply", "load-css"].into_iter().collect();
let actual_functions: BTreeSet<&str> = sass_builtin_module_function_names("meta")
.iter()
.copied()
.collect();
let actual_mixins: BTreeSet<&str> = sass_builtin_module_mixin_names("meta")
.iter()
.copied()
.collect();
assert_eq!(
actual_functions, pinned_functions,
"sass:meta function allowlist drifted from pinned Sass 1.77 surface; \
update both the allowlist and this pinned set together"
);
assert_eq!(
actual_mixins, pinned_mixins,
"sass:meta mixin allowlist drifted from pinned Sass 1.77 surface; \
update both the allowlist and this pinned set together"
);
}
#[test]
fn local_composes_flags_real_same_file_typo() {
let source = ".base { color: red; }\n.button { composes: missing; }\n";
let diagnostics = summarize_omena_query_css_modules_local_composes_style_diagnostics(
"/tmp/foo.module.scss",
source,
);
assert_eq!(diagnostics.len(), 1, "expected one missingComposedSelector");
assert_eq!(diagnostics[0].code, "missingComposedSelector");
assert!(
diagnostics[0].message.contains("not found in this file"),
"message should reference same-file resolution, got: {}",
diagnostics[0].message
);
}
#[test]
fn local_composes_keeps_resolvable_target_silent() {
let source = ".base { color: red; }\n.button { composes: base; }\n";
let diagnostics = summarize_omena_query_css_modules_local_composes_style_diagnostics(
"/tmp/foo.module.scss",
source,
);
assert!(
diagnostics.is_empty(),
"resolvable local composes target should not be flagged, got: {diagnostics:?}"
);
}
#[test]
fn local_composes_does_not_flag_external_target_without_source() {
let source = ".button { composes: shared from './other.module.scss'; color: blue; }\n";
let diagnostics = summarize_omena_query_css_modules_local_composes_style_diagnostics(
"/tmp/foo.module.scss",
source,
);
assert!(
diagnostics.is_empty(),
"external composes target must not be flagged without cross-file source, got: {diagnostics:?}"
);
}
#[test]
fn local_composes_does_not_flag_global_target() {
let source = ".button { composes: someGlobal from global; color: blue; }\n";
let diagnostics = summarize_omena_query_css_modules_local_composes_style_diagnostics(
"/tmp/foo.module.scss",
source,
);
assert!(
diagnostics.is_empty(),
"global composes target must not be flagged, got: {diagnostics:?}"
);
}
#[test]
fn single_file_summary_includes_local_composes_typo() -> Result<(), String> {
let style_uri = "/tmp/foo.module.scss";
let source = ".base { color: red; }\n.button { composes: missing; }\n";
let candidates = summarize_omena_query_style_hover_candidates(style_uri, source)
.ok_or("hover candidates")?;
let summary = summarize_omena_query_style_diagnostics_for_file_with_local_composes(
style_uri,
source,
candidates.candidates.as_slice(),
);
assert!(
summary
.diagnostics
.iter()
.any(|diagnostic| diagnostic.code == "missingComposedSelector"),
"bare single-file summary should surface the local composes typo, got: {:?}",
summary.diagnostics
);
assert_eq!(summary.diagnostic_count, summary.diagnostics.len());
Ok(())
}
#[test]
fn single_file_summary_does_not_flag_external_composes() -> Result<(), String> {
let style_uri = "/tmp/foo.module.scss";
let source = ".button { composes: shared from './other.module.scss'; color: blue; }\n";
let candidates = summarize_omena_query_style_hover_candidates(style_uri, source)
.ok_or("hover candidates")?;
let summary = summarize_omena_query_style_diagnostics_for_file_with_local_composes(
style_uri,
source,
candidates.candidates.as_slice(),
);
assert!(
!summary.diagnostics.iter().any(|diagnostic| {
diagnostic.code == "missingComposedSelector"
|| diagnostic.code == "missingComposedModule"
}),
"external composes target must not be flagged in single-file mode, got: {:?}",
summary.diagnostics
);
Ok(())
}
}