Add a test demonstrating ERB language loading bug (#32278)

Fixes https://github.com/zed-industries/zed/issues/12174

Release Notes:

- Fixed a bug where ERB files were not parsed correctly when the
languages were initially loaded.
This commit is contained in:
Max Brunsfeld 2025-06-10 21:03:42 -07:00 committed by GitHub
parent ad206a6a97
commit 72de3143c8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 145 additions and 18 deletions

View file

@ -7,6 +7,7 @@ use crate::{
use anyhow::Context as _;
use collections::HashMap;
use futures::FutureExt;
use gpui::SharedString;
use std::{
borrow::Cow,
cmp::{self, Ordering, Reverse},
@ -183,6 +184,13 @@ enum ParseStepLanguage {
}
impl ParseStepLanguage {
fn name(&self) -> SharedString {
match self {
ParseStepLanguage::Loaded { language } => language.name().0,
ParseStepLanguage::Pending { name } => name.into(),
}
}
fn id(&self) -> Option<LanguageId> {
match self {
ParseStepLanguage::Loaded { language } => Some(language.id),
@ -415,7 +423,9 @@ impl SyntaxSnapshot {
.and_then(|language| language.ok())
.is_some()
{
resolved_injection_ranges.push(layer.range.to_offset(text));
let range = layer.range.to_offset(text);
log::trace!("reparse range {range:?} for language {language_name:?}");
resolved_injection_ranges.push(range);
}
cursor.next(text);
@ -442,7 +452,10 @@ impl SyntaxSnapshot {
invalidated_ranges: Vec<Range<usize>>,
registry: Option<&Arc<LanguageRegistry>>,
) {
log::trace!("reparse. invalidated ranges:{:?}", invalidated_ranges);
log::trace!(
"reparse. invalidated ranges:{:?}",
LogOffsetRanges(&invalidated_ranges, text),
);
let max_depth = self.layers.summary().max_depth;
let mut cursor = self.layers.cursor::<SyntaxLayerSummary>(text);
@ -470,6 +483,13 @@ impl SyntaxSnapshot {
loop {
let step = queue.pop();
let position = if let Some(step) = &step {
log::trace!(
"parse step depth:{}, range:{:?}, language:{} ({:?})",
step.depth,
LogAnchorRange(&step.range, text),
step.language.name(),
step.language.id(),
);
SyntaxLayerPosition {
depth: step.depth,
range: step.range.clone(),
@ -568,13 +588,13 @@ impl SyntaxSnapshot {
.to_ts_point();
}
if let Some((SyntaxLayerContent::Parsed { tree: old_tree, .. }, layer_start)) =
old_layer.map(|layer| (&layer.content, layer.range.start))
if let Some((SyntaxLayerContent::Parsed { tree: old_tree, .. }, layer_range)) =
old_layer.map(|layer| (&layer.content, layer.range.clone()))
{
log::trace!(
"existing layer. language:{}, start:{:?}, ranges:{:?}",
"existing layer. language:{}, range:{:?}, included_ranges:{:?}",
language.name(),
LogPoint(layer_start.to_point(text)),
LogAnchorRange(&layer_range, text),
LogIncludedRanges(&old_tree.included_ranges())
);
@ -613,7 +633,7 @@ impl SyntaxSnapshot {
}
log::trace!(
"update layer. language:{}, start:{:?}, included_ranges:{:?}",
"update layer. language:{}, range:{:?}, included_ranges:{:?}",
language.name(),
LogAnchorRange(&step.range, text),
LogIncludedRanges(&included_ranges),
@ -761,28 +781,36 @@ impl SyntaxSnapshot {
#[cfg(debug_assertions)]
fn check_invariants(&self, text: &BufferSnapshot) {
let mut max_depth = 0;
let mut prev_range: Option<Range<Anchor>> = None;
let mut prev_layer: Option<(Range<Anchor>, Option<LanguageId>)> = None;
for layer in self.layers.iter() {
match Ord::cmp(&layer.depth, &max_depth) {
Ordering::Less => {
panic!("layers out of order")
}
Ordering::Equal => {
if let Some(prev_range) = prev_range {
if let Some((prev_range, prev_language_id)) = prev_layer {
match layer.range.start.cmp(&prev_range.start, text) {
Ordering::Less => panic!("layers out of order"),
Ordering::Equal => {
assert!(layer.range.end.cmp(&prev_range.end, text).is_ge())
}
Ordering::Equal => match layer.range.end.cmp(&prev_range.end, text) {
Ordering::Less => panic!("layers out of order"),
Ordering::Equal => {
if layer.content.language_id() < prev_language_id {
panic!("layers out of order")
}
}
Ordering::Greater => {}
},
Ordering::Greater => {}
}
}
prev_layer = Some((layer.range.clone(), layer.content.language_id()));
}
Ordering::Greater => {
prev_layer = None;
}
Ordering::Greater => {}
}
max_depth = layer.depth;
prev_range = Some(layer.range.clone());
}
}
@ -1642,7 +1670,7 @@ impl Ord for ParseStep {
Ord::cmp(&other.depth, &self.depth)
.then_with(|| Ord::cmp(&range_b.start, &range_a.start))
.then_with(|| Ord::cmp(&range_a.end, &range_b.end))
.then_with(|| self.language.id().cmp(&other.language.id()))
.then_with(|| other.language.id().cmp(&self.language.id()))
}
}
@ -1888,6 +1916,7 @@ impl ToTreeSitterPoint for Point {
struct LogIncludedRanges<'a>(&'a [tree_sitter::Range]);
struct LogPoint(Point);
struct LogAnchorRange<'a>(&'a Range<Anchor>, &'a text::BufferSnapshot);
struct LogOffsetRanges<'a>(&'a [Range<usize>], &'a text::BufferSnapshot);
struct LogChangedRegions<'a>(&'a ChangeRegionSet, &'a text::BufferSnapshot);
impl fmt::Debug for LogIncludedRanges<'_> {
@ -1909,6 +1938,16 @@ impl fmt::Debug for LogAnchorRange<'_> {
}
}
impl fmt::Debug for LogOffsetRanges<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list()
.entries(self.0.iter().map(|range| {
LogPoint(range.start.to_point(self.1))..LogPoint(range.end.to_point(self.1))
}))
.finish()
}
}
impl fmt::Debug for LogChangedRegions<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list()