meta_language/program_representation/
edit.rs1use super::{
2 ProgramRange, ProgramRepresentation, ProgramRepresentationError, scope_by_id,
3 validate_identifier,
4};
5
6impl ProgramRepresentation {
7 #[must_use]
9 pub fn query_syntax(&self, term: &str) -> Vec<ProgramRange> {
10 self.source_mappings
11 .iter()
12 .filter(|mapping| mapping.term == term)
13 .map(|mapping| mapping.range)
14 .collect()
15 }
16
17 pub fn replace(
19 &self,
20 range: ProgramRange,
21 replacement: &str,
22 ) -> Result<Self, ProgramRepresentationError> {
23 self.validate_range(range)?;
24 let mut edited = self.source.clone();
25 edited.replace_range(range.start..range.end, replacement);
26 self.reparse_edit(&edited)
27 }
28
29 pub fn insert(
31 &self,
32 offset: usize,
33 inserted: &str,
34 ) -> Result<Self, ProgramRepresentationError> {
35 self.replace(ProgramRange::new(offset, offset), inserted)
36 }
37
38 pub fn delete(&self, range: ProgramRange) -> Result<Self, ProgramRepresentationError> {
40 self.replace(range, "")
41 }
42
43 pub fn clone_range(
45 &self,
46 range: ProgramRange,
47 destination: usize,
48 ) -> Result<Self, ProgramRepresentationError> {
49 self.validate_range(range)?;
50 self.insert(destination, &self.source[range.start..range.end])
51 }
52
53 pub fn move_range(
55 &self,
56 range: ProgramRange,
57 destination: usize,
58 ) -> Result<Self, ProgramRepresentationError> {
59 self.validate_range(range)?;
60 self.validate_range(ProgramRange::new(destination, destination))?;
61 if destination > range.start && destination < range.end {
62 return Err(ProgramRepresentationError::DestinationInsideRange);
63 }
64 if destination == range.start || destination == range.end {
65 return Ok(self.clone());
66 }
67 let fragment = &self.source[range.start..range.end];
68 let mut without = self.source.clone();
69 without.replace_range(range.start..range.end, "");
70 let adjusted = if destination > range.end {
71 destination - (range.end - range.start)
72 } else {
73 destination
74 };
75 without.insert_str(adjusted, fragment);
76 self.reparse_edit(&without)
77 }
78
79 pub fn rename_binding(
81 &self,
82 binding_id: &str,
83 replacement: &str,
84 ) -> Result<Self, ProgramRepresentationError> {
85 let binding = self
86 .bindings
87 .iter()
88 .find(|binding| binding.id == binding_id)
89 .ok_or_else(|| ProgramRepresentationError::UnknownBinding(binding_id.to_string()))?;
90 validate_identifier(replacement, self.language)?;
91 if binding.name == replacement {
92 return Ok(self.clone());
93 }
94 let binding_scope = scope_by_id(&self.scopes, &binding.scope);
95 if let Some(conflict) = self.bindings.iter().find(|candidate| {
96 candidate.id != binding.id
97 && candidate.name == replacement
98 && (candidate.scope == binding.scope
99 || (range_inside_scope(
100 scope_by_id(&self.scopes, &candidate.scope).range,
101 binding_scope.range,
102 ) && binding.references.iter().any(|reference| {
103 range_inside_scope(
104 *reference,
105 scope_by_id(&self.scopes, &candidate.scope).range,
106 )
107 }))
108 || (range_inside_scope(
109 binding_scope.range,
110 scope_by_id(&self.scopes, &candidate.scope).range,
111 ) && candidate
112 .references
113 .iter()
114 .any(|reference| range_inside_scope(*reference, binding_scope.range))))
115 }) {
116 return Err(ProgramRepresentationError::CaptureConflict {
117 identifier: replacement.to_string(),
118 offset: conflict.declaration.start,
119 });
120 }
121 if let Some(reference) = self.unresolved_references.iter().find(|reference| {
122 reference.name == replacement
123 && reference.range.start >= binding_scope.range.start
124 && reference.range.end <= binding_scope.range.end
125 }) {
126 return Err(ProgramRepresentationError::CaptureConflict {
127 identifier: replacement.to_string(),
128 offset: reference.range.start,
129 });
130 }
131 let mut edits = std::iter::once(binding.declaration)
132 .chain(binding.references.iter().copied())
133 .map(|range| {
134 let shorthand = self.source_mappings.iter().any(|mapping| {
135 mapping.range == range
136 && shorthand_terms(self.language).contains(&mapping.term.as_str())
137 });
138 let text = if shorthand {
139 format!("{}: {replacement}", binding.name)
140 } else {
141 replacement.to_string()
142 };
143 (range, text)
144 })
145 .collect::<Vec<_>>();
146 edits.sort_by_key(|(range, _)| std::cmp::Reverse(range.start));
147 let mut edited = self.source.clone();
148 for (range, text) in &edits {
149 edited.replace_range(range.start..range.end, text);
150 }
151 let renamed = self.reparse_edit(&edited)?;
152 if let Some(offset) =
154 first_resolution_change(self, &renamed, &binding.id, replacement, &edits)
155 {
156 return Err(ProgramRepresentationError::CaptureConflict {
157 identifier: replacement.to_string(),
158 offset,
159 });
160 }
161 Ok(renamed)
162 }
163
164 fn validate_range(&self, range: ProgramRange) -> Result<(), ProgramRepresentationError> {
165 if range.start > range.end
166 || range.end > self.source.len()
167 || !self.source.is_char_boundary(range.start)
168 || !self.source.is_char_boundary(range.end)
169 {
170 return Err(ProgramRepresentationError::InvalidRange {
171 start: range.start,
172 end: range.end,
173 });
174 }
175 Ok(())
176 }
177
178 fn reparse_edit(&self, source: &str) -> Result<Self, ProgramRepresentationError> {
179 let reparsed = Self::analyze(source, self.language, self.project.clone())?;
180 if !reparsed.network.verify_full_match(None).is_clean() {
181 return Err(ProgramRepresentationError::InvalidEdit);
182 }
183 Ok(reparsed)
184 }
185}
186
187fn shorthand_terms(language: &str) -> &'static [&'static str] {
188 match language {
189 "JavaScript" => &[
190 "shorthand_property_identifier",
191 "shorthand_property_identifier_pattern",
192 ],
193 "Rust" => &["shorthand_field_initializer"],
194 _ => &[],
195 }
196}
197
198#[derive(PartialEq, Eq)]
199enum Resolved<'a> {
200 Binding {
201 name: &'a str,
202 kind: &'a str,
203 declaration: ProgramRange,
204 references: Vec<ProgramRange>,
205 },
206 Unresolved {
207 name: &'a str,
208 range: ProgramRange,
209 },
210}
211
212impl Resolved<'_> {
213 const fn offset(&self) -> usize {
214 match self {
215 Self::Binding { declaration, .. } => declaration.start,
216 Self::Unresolved { range, .. } => range.start,
217 }
218 }
219}
220
221fn first_resolution_change(
225 original: &ProgramRepresentation,
226 renamed: &ProgramRepresentation,
227 binding_id: &str,
228 replacement: &str,
229 edits: &[(ProgramRange, String)],
230) -> Option<usize> {
231 let shift = |offset: usize| {
232 edits
233 .iter()
234 .filter(|(range, _)| range.end <= offset)
235 .fold(offset, |total, (range, text)| {
236 total + text.len() - (range.end - range.start)
237 })
238 };
239 let moved = |range: ProgramRange| {
240 let edit = edits.iter().find(|(candidate, _)| *candidate == range);
241 let from = shift(range.start) + edit.map_or(0, |(_, text)| text.len() - replacement.len());
242 let length = edit.map_or(range.end - range.start, |_| replacement.len());
243 ProgramRange::new(from, from + length)
244 };
245 let expected = original
246 .bindings
247 .iter()
248 .map(|candidate| Resolved::Binding {
249 name: if candidate.id == binding_id {
250 replacement
251 } else {
252 &candidate.name
253 },
254 kind: &candidate.kind,
255 declaration: moved(candidate.declaration),
256 references: candidate.references.iter().copied().map(moved).collect(),
257 })
258 .chain(
259 original
260 .unresolved_references
261 .iter()
262 .map(|reference| Resolved::Unresolved {
263 name: &reference.name,
264 range: moved(reference.range),
265 }),
266 )
267 .collect::<Vec<_>>();
268 let actual = renamed
269 .bindings
270 .iter()
271 .map(|candidate| Resolved::Binding {
272 name: &candidate.name,
273 kind: &candidate.kind,
274 declaration: candidate.declaration,
275 references: candidate.references.clone(),
276 })
277 .chain(
278 renamed
279 .unresolved_references
280 .iter()
281 .map(|reference| Resolved::Unresolved {
282 name: &reference.name,
283 range: reference.range,
284 }),
285 )
286 .collect::<Vec<_>>();
287 (0..expected.len().max(actual.len())).find_map(|index| {
288 let (left, right) = (expected.get(index), actual.get(index));
289 (left != right).then(|| right.or(left).map_or(0, Resolved::offset))
290 })
291}
292
293const fn range_inside_scope(range: ProgramRange, scope: ProgramRange) -> bool {
294 range.start >= scope.start && range.end <= scope.end
295}