mirror of
https://github.com/github/codeql.git
synced 2026-06-05 13:37:06 +02:00
yeast: type-check for missing required fields
Add FieldCardinality to Schema to track required/multiple per field, populated from the ast_types.yml suffixes (bare = required single, ? = optional single, + = required multiple, * = optional multiple). dump_ast_with_type_errors now emits: <-- ERROR: missing required field 'name' for any node in the output AST whose declared schema requires a field that is absent from the actual node. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -273,6 +273,16 @@ fn dump_node(
|
||||
}
|
||||
}
|
||||
|
||||
// Check for required fields that are absent
|
||||
if let Some((schema, _, _)) = type_check {
|
||||
for (field_id, field_name) in schema.required_fields_for_kind(node.kind_name()) {
|
||||
if !node.fields.contains_key(&field_id) {
|
||||
let name = field_name.unwrap_or("child");
|
||||
writeln!(out, "{prefix} <-- ERROR: missing required field '{name}'").unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Unnamed children — skip unnamed tokens (keywords, punctuation)
|
||||
if let Some(children) = node.fields.get(&CHILD_FIELD) {
|
||||
let child_type_check = type_check.map(|(schema, _, _)| {
|
||||
|
||||
@@ -314,6 +314,14 @@ fn apply_yaml_to_schema(
|
||||
node_types.sort_by(|a, b| a.kind.cmp(&b.kind).then(a.named.cmp(&b.named)));
|
||||
node_types.dedup_by(|a, b| a.kind == b.kind && a.named == b.named);
|
||||
schema.set_field_types(parent_kind, field_id, node_types);
|
||||
schema.set_field_cardinality(
|
||||
parent_kind,
|
||||
field_id,
|
||||
crate::schema::FieldCardinality {
|
||||
multiple: spec.multiple,
|
||||
required: spec.required,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,15 @@ pub struct NodeType {
|
||||
pub named: bool,
|
||||
}
|
||||
|
||||
/// Multiplicity/optionality of a field declaration.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct FieldCardinality {
|
||||
/// Whether the field may hold more than one child.
|
||||
pub multiple: bool,
|
||||
/// Whether at least one child must be present.
|
||||
pub required: bool,
|
||||
}
|
||||
|
||||
/// A schema defining node kinds and field names for the output AST.
|
||||
/// Built from a node-types.yml file, independent of any tree-sitter grammar.
|
||||
///
|
||||
@@ -32,6 +41,7 @@ pub struct Schema {
|
||||
kind_names: BTreeMap<KindId, &'static str>,
|
||||
next_kind_id: KindId,
|
||||
field_types: BTreeMap<(String, FieldId), Vec<NodeType>>,
|
||||
field_cardinalities: BTreeMap<(String, FieldId), FieldCardinality>,
|
||||
supertypes: BTreeMap<String, Vec<NodeType>>,
|
||||
}
|
||||
|
||||
@@ -52,6 +62,7 @@ impl Schema {
|
||||
kind_names: BTreeMap::new(),
|
||||
next_kind_id: 1, // 0 is reserved
|
||||
field_types: BTreeMap::new(),
|
||||
field_cardinalities: BTreeMap::new(),
|
||||
supertypes: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
@@ -196,6 +207,42 @@ impl Schema {
|
||||
.get(&(parent_kind.to_string(), field_id))
|
||||
}
|
||||
|
||||
pub fn set_field_cardinality(
|
||||
&mut self,
|
||||
parent_kind: &str,
|
||||
field_id: FieldId,
|
||||
cardinality: FieldCardinality,
|
||||
) {
|
||||
self.field_cardinalities
|
||||
.insert((parent_kind.to_string(), field_id), cardinality);
|
||||
}
|
||||
|
||||
/// Returns the declared cardinality for a field, if known.
|
||||
pub fn field_cardinality(
|
||||
&self,
|
||||
parent_kind: &str,
|
||||
field_id: FieldId,
|
||||
) -> Option<FieldCardinality> {
|
||||
self.field_cardinalities
|
||||
.get(&(parent_kind.to_string(), field_id))
|
||||
.copied()
|
||||
}
|
||||
|
||||
/// Returns an iterator over all `(field_id, field_name)` pairs that are
|
||||
/// declared as required (`required: true`) for the given `parent_kind`.
|
||||
pub fn required_fields_for_kind<'a>(
|
||||
&'a self,
|
||||
parent_kind: &'a str,
|
||||
) -> impl Iterator<Item = (FieldId, Option<&'static str>)> + 'a {
|
||||
self.field_cardinalities
|
||||
.iter()
|
||||
.filter(move |((kind, _), card)| kind == parent_kind && card.required)
|
||||
.map(move |((_, field_id), _)| {
|
||||
let name = self.field_name_for_id(*field_id);
|
||||
(*field_id, name)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_supertype_members(&mut self, supertype: &str, node_types: Vec<NodeType>) {
|
||||
self.supertypes.insert(supertype.to_string(), node_types);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user