use serde_json::Value as JsonValue;
use crate::diagnostics::{DiagnosticLocation, DocId, EntityId};
use super::super::engine::{LintContext, variable_values};
use super::super::index::*;
use super::super::source::{DocumentKind, SourceDocument};
use super::super::syntax::item_location;
use super::marshal::{
expanded_variable_toml_json, parsed_toml_json, selected_schema_field, selected_value_field,
};
use super::{
QualifierLintField, RegisteredLintEntity, RegisteredLintField, RegisteredLintSelector,
VariableLintField, WorkspaceLintField,
};
pub(super) struct RegisteredLintTargetInstance {
pub(super) entity: EntityId,
pub(super) location: DiagnosticLocation,
pub(super) data: JsonValue,
}
pub(super) fn registered_lint_targets(
ctx: &LintContext,
selector: &RegisteredLintSelector,
) -> Vec<RegisteredLintTargetInstance> {
match selector.entity {
RegisteredLintEntity::Workspace => {
registered_workspace_targets(ctx, selector.field.as_ref())
}
RegisteredLintEntity::Qualifier => {
registered_qualifier_targets(ctx, selector.field.as_ref())
}
RegisteredLintEntity::Variable => registered_variable_targets(ctx, selector.field.as_ref()),
RegisteredLintEntity::Value => registered_value_targets(ctx, selector.field.as_ref()),
RegisteredLintEntity::Schema => registered_schema_targets(ctx, selector.field.as_ref()),
}
}
pub(super) fn registered_lint_output_location(
ctx: &LintContext,
target: &RegisteredLintTargetInstance,
field: Option<&RegisteredLintField>,
) -> DiagnosticLocation {
let Some(field) = field else {
return target.location.clone();
};
match (&target.entity, field) {
(EntityId::Workspace, RegisteredLintField::Workspace(_)) => ctx
.index
.manifest
.as_ref()
.map(|manifest| registered_workspace_location(ctx, manifest, Some(field)))
.unwrap_or_else(|| target.location.clone()),
(EntityId::Qualifier { id }, RegisteredLintField::Qualifier(_)) => ctx
.index
.qualifiers
.get(id)
.map(|qualifier| registered_qualifier_location(ctx, qualifier, Some(field)))
.unwrap_or_else(|| target.location.clone()),
(EntityId::Variable { id }, RegisteredLintField::Variable(_)) => ctx
.index
.variables
.get(id)
.map(|variable| registered_variable_location(ctx, variable, Some(field)))
.unwrap_or_else(|| target.location.clone()),
(EntityId::Value { variable, key }, RegisteredLintField::Value(_)) => {
find_value(ctx, variable, key)
.map(|value| registered_value_location(value, Some(field)))
.unwrap_or_else(|| target.location.clone())
}
(EntityId::Schema { path }, RegisteredLintField::Schema(_)) => ctx
.source
.document_by_path(path)
.map(|document| registered_schema_location(document, Some(field)))
.unwrap_or_else(|| target.location.clone()),
_ => target.location.clone(),
}
}
fn registered_workspace_targets(
ctx: &LintContext,
field: Option<&RegisteredLintField>,
) -> Vec<RegisteredLintTargetInstance> {
let Some(manifest) = &ctx.index.manifest else {
return Vec::new();
};
let Some(document) = ctx.source.documents.get(&manifest.doc) else {
return Vec::new();
};
let extends = match &manifest.extends {
WorkspaceExtendsCollection::Sources { values, .. } => values
.iter()
.map(|value| value.source.clone())
.collect::<Vec<_>>(),
WorkspaceExtendsCollection::Missing | WorkspaceExtendsCollection::Invalid { .. } => {
Vec::new()
}
};
vec![RegisteredLintTargetInstance {
entity: EntityId::Workspace,
location: registered_workspace_location(ctx, manifest, field),
data: serde_json::json!({
"kind": "workspace",
"root": ctx.source.root.display().to_string(),
"manifest": {
"uri": document.uri,
"path": document.path,
"toml": parsed_toml_json(ctx, manifest.doc),
},
"extends": extends,
}),
}]
}
fn registered_qualifier_targets(
ctx: &LintContext,
field: Option<&RegisteredLintField>,
) -> Vec<RegisteredLintTargetInstance> {
ctx.index
.qualifiers
.values()
.filter_map(|qualifier| {
let document = ctx.source.documents.get(&qualifier.doc)?;
Some(RegisteredLintTargetInstance {
entity: EntityId::Qualifier {
id: qualifier.id.clone(),
},
location: registered_qualifier_location(ctx, qualifier, field),
data: serde_json::json!({
"kind": "qualifier",
"id": qualifier.id,
"uri": document.uri,
"path": document.path,
"toml": parsed_toml_json(ctx, qualifier.doc),
}),
})
})
.collect()
}
fn registered_variable_targets(
ctx: &LintContext,
field: Option<&RegisteredLintField>,
) -> Vec<RegisteredLintTargetInstance> {
ctx.index
.variables
.values()
.filter_map(|variable| {
let document = ctx.source.documents.get(&variable.doc)?;
Some(RegisteredLintTargetInstance {
entity: EntityId::Variable {
id: variable.id.clone(),
},
location: registered_variable_location(ctx, variable, field),
data: serde_json::json!({
"kind": "variable",
"id": variable.id,
"uri": document.uri,
"path": document.path,
"toml": expanded_variable_toml_json(ctx, variable),
}),
})
})
.collect()
}
fn registered_value_targets(
ctx: &LintContext,
field: Option<&RegisteredLintField>,
) -> Vec<RegisteredLintTargetInstance> {
let mut targets = Vec::new();
for variable in ctx.index.variables.values() {
let Some(variable_document) = ctx.source.documents.get(&variable.doc) else {
continue;
};
for value in variable_values(ctx, variable) {
targets.push(RegisteredLintTargetInstance {
entity: EntityId::Value {
variable: value.variable_id.clone(),
key: value.key.clone(),
},
location: registered_value_location(value, field),
data: serde_json::json!({
"kind": "value",
"name": value.key,
"value": value.value,
"origin": value_origin_json(value),
"selected": selected_value_field(&value.value, field),
"variable": {
"id": variable.id,
"uri": variable_document.uri,
"path": variable_document.path,
},
}),
});
}
}
targets
}
fn value_origin_json(value: &ValueNode) -> JsonValue {
match &value.origin {
ValueOrigin::Inline { variable_doc } => serde_json::json!({
"kind": "inline",
"doc": variable_doc,
}),
}
}
fn registered_schema_targets(
ctx: &LintContext,
field: Option<&RegisteredLintField>,
) -> Vec<RegisteredLintTargetInstance> {
ctx.source
.documents
.values()
.filter(|document| matches!(&document.kind, DocumentKind::Schema))
.filter_map(|document| {
let schema = ctx.index.schemas.get(&document.path)?;
let json = schema.json.as_ref()?;
Some(RegisteredLintTargetInstance {
entity: EntityId::Schema {
path: document.path.clone(),
},
location: registered_schema_location(document, field),
data: serde_json::json!({
"kind": "schema",
"uri": document.uri,
"path": document.path,
"json": json,
"selected": selected_schema_field(json, field),
}),
})
})
.collect()
}
fn find_value<'a>(ctx: &'a LintContext, variable: &str, key: &str) -> Option<&'a ValueNode> {
let variable = ctx.index.variables.get(variable)?;
variable_values(ctx, variable).find(|value| value.key.as_str() == key)
}
fn registered_workspace_location(
ctx: &LintContext,
manifest: &ManifestNode,
field: Option<&RegisteredLintField>,
) -> DiagnosticLocation {
match field {
Some(RegisteredLintField::Workspace(WorkspaceLintField::Extends)) => {
toml_root_item_location(ctx, manifest.doc, "extends")
.unwrap_or_else(|| manifest.location.clone())
}
_ => manifest.location.clone(),
}
}
fn registered_qualifier_location(
ctx: &LintContext,
qualifier: &QualifierNode,
field: Option<&RegisteredLintField>,
) -> DiagnosticLocation {
match field {
Some(RegisteredLintField::Qualifier(QualifierLintField::Description)) => {
toml_root_item_location(ctx, qualifier.doc, "description")
.unwrap_or_else(|| qualifier.location.clone())
}
Some(RegisteredLintField::Qualifier(QualifierLintField::Predicates)) => {
toml_root_item_location(ctx, qualifier.doc, "predicate")
.unwrap_or_else(|| qualifier.location.clone())
}
_ => qualifier.location.clone(),
}
}
fn registered_variable_location(
ctx: &LintContext,
variable: &VariableNode,
field: Option<&RegisteredLintField>,
) -> DiagnosticLocation {
match field {
Some(RegisteredLintField::Variable(VariableLintField::Description)) => {
toml_root_item_location(ctx, variable.doc, "description")
.unwrap_or_else(|| variable.location.clone())
}
Some(RegisteredLintField::Variable(VariableLintField::Type))
if matches!(
&variable.type_source,
TypeSourceNode::Primitive(_) | TypeSourceNode::Resource(_)
) =>
{
variable.type_source.location()
}
Some(RegisteredLintField::Variable(VariableLintField::Schema))
if matches!(&variable.type_source, TypeSourceNode::Schema(_)) =>
{
variable.type_source.location()
}
Some(RegisteredLintField::Variable(VariableLintField::Values)) => {
variable.values.location.clone()
}
Some(RegisteredLintField::Variable(VariableLintField::Resolve)) => {
toml_root_item_location(ctx, variable.doc, "resolve")
.unwrap_or_else(|| variable.resolve.location())
}
_ => variable.location.clone(),
}
}
fn registered_value_location(
value: &ValueNode,
_field: Option<&RegisteredLintField>,
) -> DiagnosticLocation {
value.location.clone()
}
fn registered_schema_location(
document: &SourceDocument,
_field: Option<&RegisteredLintField>,
) -> DiagnosticLocation {
document.document_location()
}
fn toml_root_item_location(ctx: &LintContext, doc: DocId, key: &str) -> Option<DiagnosticLocation> {
let document = ctx.source.documents.get(&doc)?;
let parsed = ctx.syntax.toml.get(&doc)?;
parsed
.root_table()?
.get(key)
.map(|item| item_location(document, item))
}