From 42e00ed513d2d64a763100204184fe2573324ec0 Mon Sep 17 00:00:00 2001 From: Arend van Beelen jr Date: Sun, 2 Jun 2024 20:58:22 +0200 Subject: [PATCH] refactor: remove `AstNode::full_source()` (#366) --- Cargo.lock | 8 +- Cargo.toml | 2 +- crates/core/src/inline_snippets.rs | 8 +- crates/core/src/marzano_binding.rs | 176 +++--------------- crates/core/src/marzano_context.rs | 4 +- crates/core/src/marzano_resolved_pattern.rs | 4 +- crates/core/src/problem.rs | 2 +- crates/core/src/text_unparser.rs | 11 +- crates/grit-pattern-matcher/Cargo.toml | 2 +- crates/grit-pattern-matcher/src/context.rs | 4 +- crates/grit-pattern-matcher/src/effects.rs | 7 +- crates/grit-pattern-matcher/src/intervals.rs | 3 +- .../src/pattern/accumulate.rs | 8 +- .../src/pattern/rewrite.rs | 7 +- .../grit-pattern-matcher/src/pattern/state.rs | 12 +- crates/grit-util/src/ast_node.rs | 4 - crates/grit-util/src/code_range.rs | 5 + crates/grit-util/src/effect_kind.rs | 5 + crates/grit-util/src/language.rs | 43 ++--- crates/grit-util/src/lib.rs | 6 +- crates/grit-util/src/parser.rs | 5 +- crates/grit-util/src/ranges.rs | 27 ++- crates/language/src/csharp.rs | 10 +- crates/language/src/css.rs | 10 +- crates/language/src/go.rs | 10 +- crates/language/src/hcl.rs | 10 +- crates/language/src/html.rs | 10 +- crates/language/src/java.rs | 10 +- crates/language/src/javascript.rs | 10 +- crates/language/src/json.rs | 10 +- crates/language/src/language.rs | 146 ++++++++++++++- crates/language/src/lib.rs | 58 ++++++ crates/language/src/markdown_block.rs | 10 +- crates/language/src/markdown_inline.rs | 10 +- crates/language/src/php.rs | 10 +- crates/language/src/php_only.rs | 10 +- crates/language/src/python.rs | 32 ++-- crates/language/src/ruby.rs | 10 +- crates/language/src/rust.rs | 10 +- crates/language/src/solidity.rs | 10 +- crates/language/src/sql.rs | 10 +- crates/language/src/target_language.rs | 34 +++- crates/language/src/toml.rs | 10 +- crates/language/src/tsx.rs | 10 +- crates/language/src/typescript.rs | 10 +- crates/language/src/vue.rs | 10 +- crates/language/src/yaml.rs | 10 +- crates/util/src/node_with_source.rs | 4 - 48 files changed, 385 insertions(+), 452 deletions(-) create mode 100644 crates/grit-util/src/effect_kind.rs diff --git a/Cargo.lock b/Cargo.lock index 2126ba16b..c42860b50 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1400,7 +1400,7 @@ dependencies = [ [[package]] name = "grit-pattern-matcher" -version = "0.2.0" +version = "0.3.0" dependencies = [ "anyhow", "elsa", @@ -1414,7 +1414,7 @@ dependencies = [ [[package]] name = "grit-util" -version = "0.2.0" +version = "0.3.0" dependencies = [ "derive_builder", "napi", @@ -1426,7 +1426,7 @@ dependencies = [ [[package]] name = "grit-wasm-bindings" -version = "0.2.0" +version = "0.3.0" dependencies = [ "ai_builtins", "anyhow", @@ -2166,7 +2166,7 @@ dependencies = [ [[package]] name = "marzano-gritmodule" -version = "0.2.0" +version = "0.3.0" dependencies = [ "anyhow", "fs-err", diff --git a/Cargo.toml b/Cargo.toml index 276bf270b..8810ff029 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ exclude = [ ] [workspace.package] -version = "0.2.0" +version = "0.3.0" authors = ["Iuvo AI, Inc.", "Grit Contributors"] description = "GritQL is a query language for searching, linting, and modifying code." repository = "https://github.com/getgrit/gritql/" diff --git a/crates/core/src/inline_snippets.rs b/crates/core/src/inline_snippets.rs index 2d07db002..0044c8d61 100644 --- a/crates/core/src/inline_snippets.rs +++ b/crates/core/src/inline_snippets.rs @@ -1,7 +1,5 @@ -use crate::marzano_binding; -use crate::marzano_binding::EffectRange; use anyhow::{anyhow, bail, Result}; -use grit_util::Language; +use grit_util::{EffectRange, Language}; use itertools::Itertools; use std::{cell::RefCell, collections::HashSet, ops::Range, rc::Rc}; @@ -113,8 +111,8 @@ fn pad_snippet( } } - let padding = padding.into_iter().collect::(); - marzano_binding::pad_snippet(&padding, snippet, language) + let padding: String = padding.into_iter().collect(); + Ok(language.pad_snippet(snippet, &padding).to_string()) } // checks on this one are likely redundant as diff --git a/crates/core/src/marzano_binding.rs b/crates/core/src/marzano_binding.rs index 69f514617..2ca063989 100644 --- a/crates/core/src/marzano_binding.rs +++ b/crates/core/src/marzano_binding.rs @@ -8,11 +8,12 @@ use grit_pattern_matcher::{ binding::Binding, constant::Constant, context::QueryContext, - effects::{Effect, EffectKind}, + effects::Effect, pattern::{get_top_level_effects, FileRegistry, ResolvedPattern}, }; use grit_util::{ - AnalysisLogBuilder, AnalysisLogs, AstNode, ByteRange, CodeRange, Language, Position, Range, + AnalysisLogBuilder, AnalysisLogs, AstNode, ByteRange, CodeRange, EffectKind, EffectRange, + Language, Position, Range, }; use itertools::{EitherOrBoth, Itertools}; use marzano_language::language::{FieldId, MarzanoLanguage}; @@ -52,138 +53,13 @@ impl PartialEq for MarzanoBinding<'_> { } } -pub(crate) fn pad_snippet(padding: &str, snippet: &str, lang: &impl Language) -> Result { - let mut lines = snippet.split('\n'); - let mut result = lines.next().unwrap_or_default().to_string(); - - // Add the rest of lines in the snippet with padding - let skip_ranges = lang.get_skip_padding_ranges_for_snippet(snippet); - for line in lines { - let index = get_slice_byte_offset(snippet, line); - if !is_index_in_ranges(index, &skip_ranges) { - result.push_str(&format!("\n{}{}", &padding, line)) - } else { - result.push_str(&format!("\n{}", line)) - } - } - Ok(result) -} - -fn adjust_ranges(substitutions: &mut [(EffectRange, String)], index: usize, delta: isize) { - for (EffectRange { range, .. }, _) in substitutions.iter_mut() { - if range.start >= index { - range.start = (range.start as isize + delta) as usize; - } - if range.end >= index { - range.end = (range.end as isize + delta) as usize; - } - } -} - -pub(crate) fn is_index_in_ranges(index: u32, skip_ranges: &[CodeRange]) -> bool { - skip_ranges - .iter() - .any(|r| r.start <= index && index < r.end) -} - -// safety ensure that sup and sub are slices of the same string. -fn get_slice_byte_offset(sup: &str, sub: &str) -> u32 { - unsafe { sub.as_ptr().byte_offset_from(sup.as_ptr()) }.unsigned_abs() as u32 -} - -// in multiline snippets, remove padding from every line equal to the padding of the first line, -// such that the first line is left-aligned. -fn adjust_padding<'a>( - src: &'a str, - range: &CodeRange, - skip_ranges: &[CodeRange], - new_padding: Option, - offset: usize, - substitutions: &mut [(EffectRange, String)], - lang: &impl Language, -) -> Result> { - if let Some(new_padding) = new_padding { - let newline_index = src[0..range.start as usize].rfind('\n'); - let pad_strip_amount = if let Some(index) = newline_index { - src[index..range.start as usize] - .chars() - .take_while(|c| c.is_whitespace()) - .count() - - 1 - } else { - 0 - }; - let mut result = String::new(); - let snippet = &src[range.start as usize..range.end as usize]; - let mut lines = snippet.split('\n'); - // assumes codebase uses spaces for indentation - let delta: isize = (new_padding as isize) - (pad_strip_amount as isize); - let padding = " ".repeat(pad_strip_amount); - let new_padding = " ".repeat(new_padding); - result.push_str(lines.next().unwrap_or_default()); - for line in lines { - result.push('\n'); - let index = get_slice_byte_offset(src, line); - if !is_index_in_ranges(index, skip_ranges) { - if line.trim().is_empty() { - adjust_ranges(substitutions, offset + result.len(), -(line.len() as isize)); - continue; - } - adjust_ranges(substitutions, offset + result.len(), delta); - let line = line.strip_prefix(&padding).ok_or_else(|| { - anyhow!( - "expected line \n{}\n to start with {} spaces, code is either not indented with spaces, or does not consistently indent code blocks", - line, - pad_strip_amount - ) - })?; - result.push_str(&new_padding); - result.push_str(line); - } else { - result.push_str(line) - } - } - for (_, snippet) in substitutions.iter_mut() { - *snippet = pad_snippet(&new_padding, snippet, lang)?; - } - Ok(result.into()) - } else { - Ok(src[range.start as usize..range.end as usize].into()) - } -} - -#[derive(Debug, Clone)] -pub(crate) struct EffectRange { - pub(crate) kind: EffectKind, - pub(crate) range: StdRange, -} - -impl EffectRange { - pub(crate) fn new(kind: EffectKind, range: StdRange) -> Self { - Self { kind, range } - } - - pub(crate) fn start(&self) -> usize { - self.range.start - } - - // The range which is actually edited by this effect - // This is used for most operations, but does not account for expansion from deleted commas - pub(crate) fn effective_range(&self) -> StdRange { - match self.kind { - EffectKind::Rewrite => self.range.clone(), - EffectKind::Insert => self.range.end..self.range.end, - } - } -} - #[allow(clippy::too_many_arguments, clippy::type_complexity)] pub(crate) fn linearize_binding<'a, Q: QueryContext>( language: &Q::Language<'a>, effects: &[Effect<'a, Q>], files: &FileRegistry<'a, Q>, memo: &mut HashMap>, - source: Q::Node<'a>, + source: &Q::Node<'a>, range: CodeRange, distributed_indent: Option, logs: &mut AnalysisLogs, @@ -195,32 +71,29 @@ pub(crate) fn linearize_binding<'a, Q: QueryContext>( .map(|effect| { let binding = effect.binding; let binding_range = Binding::code_range(&binding, language); - if let (Some(src), Some(range)) = (binding.source(), binding_range.as_ref()) { + if let Some(range) = binding_range.as_ref() { match effect.kind { EffectKind::Rewrite => { if let Some(o) = memo.get(range) { - if let Some(s) = o { - return Ok((binding, s.to_owned().into(), effect.kind)); + let aligned_snippet = if let Some(s) = o { + s.clone().into() } else { let skip_padding_ranges = binding .as_node() .map(|n| language.get_skip_padding_ranges(&n)) .unwrap_or_default(); - return Ok(( - binding, - adjust_padding( - src, - range, - &skip_padding_ranges, - distributed_indent, - 0, - &mut [], - language, - )?, - effect.kind, - )); - } + language.align_padding( + source, + range, + &skip_padding_ranges, + distributed_indent, + 0, + &mut [], + ) + }; + + return Ok((binding, aligned_snippet, effect.kind)); } else { memo.insert(range.clone(), None); } @@ -266,17 +139,16 @@ pub(crate) fn linearize_binding<'a, Q: QueryContext>( }) .collect::>>()?; - let skip_padding_ranges = language.get_skip_padding_ranges(&source); + let skip_padding_ranges = language.get_skip_padding_ranges(source); // we need to update the ranges of the replacements to account for padding discrepency - let adjusted_source = adjust_padding( - source.full_source(), + let adjusted_source = language.align_padding( + source, &range, &skip_padding_ranges, distributed_indent, range.start as usize, &mut replacements, - language, - )?; + ); let (res, offset, mapping) = inline_sorted_snippets_with_offset( language, adjusted_source.to_string(), @@ -538,7 +410,7 @@ impl<'a> Binding<'a, MarzanoQueryContext> for MarzanoBinding<'a> { effects, files, memo, - node.clone(), + node, node.code_range(), distributed_indent, logs, @@ -559,7 +431,7 @@ impl<'a> Binding<'a, MarzanoQueryContext> for MarzanoBinding<'a> { memo, // ideally we should be passing list as an ast_node // a little tricky atm - parent_node.clone(), + parent_node, range, distributed_indent, logs, diff --git a/crates/core/src/marzano_context.rs b/crates/core/src/marzano_context.rs index 07813a1e7..500073662 100644 --- a/crates/core/src/marzano_context.rs +++ b/crates/core/src/marzano_context.rs @@ -238,9 +238,8 @@ impl<'a> ExecContext<'a, MarzanoQueryContext> for MarzanoContext<'a> { .cloned() .is_some() { - let code = file.tree.root_node(); let (new_src, new_ranges, adjustment_ranges) = apply_effects( - code, + &file.tree, state.effects.clone(), &state.files, &file.name, @@ -249,7 +248,6 @@ impl<'a> ExecContext<'a, MarzanoQueryContext> for MarzanoContext<'a> { logs, )?; - if let (Some(new_ranges), Some(edit_ranges)) = (new_ranges, adjustment_ranges) { let new_map = if let Some(old_map) = file.tree.source_map.as_ref() { Some(old_map.clone_with_edits(edit_ranges.iter().rev())?) diff --git a/crates/core/src/marzano_resolved_pattern.rs b/crates/core/src/marzano_resolved_pattern.rs index c37d513d0..7b2aa9d88 100644 --- a/crates/core/src/marzano_resolved_pattern.rs +++ b/crates/core/src/marzano_resolved_pattern.rs @@ -7,14 +7,14 @@ use grit_pattern_matcher::{ binding::Binding, constant::Constant, context::ExecContext, - effects::{Effect, EffectKind}, + effects::Effect, pattern::{ to_unsigned, Accessor, DynamicPattern, DynamicSnippet, DynamicSnippetPart, File, FilePtr, FileRegistry, GritCall, ListIndex, Pattern, PatternName, PatternOrResolved, ResolvedFile, ResolvedPattern, ResolvedSnippet, State, }, }; -use grit_util::{AnalysisLogs, Ast, AstNode, CodeRange, Range}; +use grit_util::{AnalysisLogs, Ast, AstNode, CodeRange, EffectKind, Range}; use im::{vector, Vector}; use marzano_language::{language::FieldId, target_language::TargetLanguage}; use marzano_util::node_with_source::NodeWithSource; diff --git a/crates/core/src/problem.rs b/crates/core/src/problem.rs index b660074c4..c4a2ed73b 100644 --- a/crates/core/src/problem.rs +++ b/crates/core/src/problem.rs @@ -503,5 +503,5 @@ impl QueryContext for MarzanoQueryContext { type ResolvedPattern<'a> = MarzanoResolvedPattern<'a>; type Language<'a> = TargetLanguage; type File<'a> = MarzanoFile<'a>; - type Tree = Tree; + type Tree<'a> = Tree; } diff --git a/crates/core/src/text_unparser.rs b/crates/core/src/text_unparser.rs index f86c775c9..d042b6c08 100644 --- a/crates/core/src/text_unparser.rs +++ b/crates/core/src/text_unparser.rs @@ -6,7 +6,7 @@ use grit_pattern_matcher::{ effects::Effect, pattern::{FileRegistry, ResolvedPattern}, }; -use grit_util::{AnalysisLogs, AstNode, CodeRange, Language}; +use grit_util::{AnalysisLogs, Ast, CodeRange, Language}; use im::Vector; use std::collections::HashMap; use std::ops::Range; @@ -28,7 +28,7 @@ type EffectOutcome = ( #[allow(clippy::too_many_arguments)] pub(crate) fn apply_effects<'a, Q: QueryContext>( - code: Q::Node<'a>, + code: &'a Q::Tree<'a>, effects: Vector>, files: &FileRegistry<'a, Q>, the_filename: &Path, @@ -44,16 +44,17 @@ pub(crate) fn apply_effects<'a, Q: QueryContext>( .filter(|effect| !effect.binding.is_suppressed(language, current_name)) .collect(); if effects.is_empty() { - return Ok((code.full_source().to_owned(), None, None)); + return Ok((code.source().to_string(), None, None)); } + let mut memo: HashMap> = HashMap::new(); let (from_inline, output_ranges, effect_ranges) = linearize_binding( language, &effects, files, &mut memo, - code.clone(), - CodeRange::new(0, code.full_source().len() as u32, code.full_source()), + &code.root_node(), + CodeRange::new(0, code.source().len() as u32, &code.source()), language.should_pad_snippet().then_some(0), logs, )?; diff --git a/crates/grit-pattern-matcher/Cargo.toml b/crates/grit-pattern-matcher/Cargo.toml index 310035461..8337a3303 100644 --- a/crates/grit-pattern-matcher/Cargo.toml +++ b/crates/grit-pattern-matcher/Cargo.toml @@ -18,7 +18,7 @@ rust.unused_crate_dependencies = "warn" anyhow = { version = "1.0.70" } elsa = { version = "1.9.0" } getrandom = { version = "0.2.11", optional = true } -grit-util = { path = "../grit-util", version = "0.2.0" } +grit-util = { path = "../grit-util", version = "0.3.0" } im = { version = "15.1.0" } itertools = { version = "0.10.5" } rand = { version = "0.8.5" } diff --git a/crates/grit-pattern-matcher/src/context.rs b/crates/grit-pattern-matcher/src/context.rs index 5e40b0062..2fcc1ab97 100644 --- a/crates/grit-pattern-matcher/src/context.rs +++ b/crates/grit-pattern-matcher/src/context.rs @@ -20,7 +20,7 @@ pub trait QueryContext: Clone + std::fmt::Debug + Sized + 'static { type ResolvedPattern<'a>: ResolvedPattern<'a, Self>; type Language<'a>: Language = Self::Node<'a>>; type File<'a>: File<'a, Self>; - type Tree: Ast + Clone; + type Tree<'a>: Ast = Self::Node<'a>> + Clone; } /// Contains context necessary for query execution. @@ -53,7 +53,7 @@ pub trait ExecContext<'a, Q: QueryContext> { ) -> Result; // FIXME: Don't depend on Grit's file handling in Context. - fn files(&self) -> &FileOwners; + fn files(&self) -> &FileOwners>; fn language(&self) -> &Q::Language<'a>; diff --git a/crates/grit-pattern-matcher/src/effects.rs b/crates/grit-pattern-matcher/src/effects.rs index 0eb3ff220..537bd9708 100644 --- a/crates/grit-pattern-matcher/src/effects.rs +++ b/crates/grit-pattern-matcher/src/effects.rs @@ -1,10 +1,5 @@ use crate::context::QueryContext; - -#[derive(Debug, Clone)] -pub enum EffectKind { - Rewrite, - Insert, -} +use grit_util::EffectKind; #[derive(Debug, Clone)] pub struct Effect<'a, Q: QueryContext> { diff --git a/crates/grit-pattern-matcher/src/intervals.rs b/crates/grit-pattern-matcher/src/intervals.rs index e27cc99d3..10b2fe16d 100644 --- a/crates/grit-pattern-matcher/src/intervals.rs +++ b/crates/grit-pattern-matcher/src/intervals.rs @@ -1,4 +1,5 @@ -use crate::{context::QueryContext, effects::EffectKind, pattern::EffectRange}; +use crate::{context::QueryContext, pattern::EffectRange}; +use grit_util::EffectKind; use std::{cmp::Ordering, ops::Range}; pub trait Interval { diff --git a/crates/grit-pattern-matcher/src/pattern/accumulate.rs b/crates/grit-pattern-matcher/src/pattern/accumulate.rs index e1f044f25..2d2f1bc96 100644 --- a/crates/grit-pattern-matcher/src/pattern/accumulate.rs +++ b/crates/grit-pattern-matcher/src/pattern/accumulate.rs @@ -5,13 +5,9 @@ use super::{ resolved_pattern::ResolvedPattern, State, }; -use crate::{ - context::ExecContext, - context::QueryContext, - effects::{Effect, EffectKind}, -}; +use crate::{context::ExecContext, context::QueryContext, effects::Effect}; use anyhow::{bail, Result}; -use grit_util::AnalysisLogs; +use grit_util::{AnalysisLogs, EffectKind}; use std::borrow::Cow; #[derive(Debug, Clone)] diff --git a/crates/grit-pattern-matcher/src/pattern/rewrite.rs b/crates/grit-pattern-matcher/src/pattern/rewrite.rs index 25618f171..74596e7c2 100644 --- a/crates/grit-pattern-matcher/src/pattern/rewrite.rs +++ b/crates/grit-pattern-matcher/src/pattern/rewrite.rs @@ -6,13 +6,10 @@ use super::{ variable_content::VariableContent, State, }; -use crate::{ - context::QueryContext, - effects::{Effect, EffectKind}, -}; +use crate::{context::QueryContext, effects::Effect}; use anyhow::{bail, Result}; use core::fmt::Debug; -use grit_util::AnalysisLogs; +use grit_util::{AnalysisLogs, EffectKind}; use std::borrow::Cow; #[derive(Debug, Clone)] diff --git a/crates/grit-pattern-matcher/src/pattern/state.rs b/crates/grit-pattern-matcher/src/pattern/state.rs index 8357e1209..c956b5c28 100644 --- a/crates/grit-pattern-matcher/src/pattern/state.rs +++ b/crates/grit-pattern-matcher/src/pattern/state.rs @@ -34,11 +34,11 @@ pub struct FileRegistry<'a, Q: QueryContext> { /// Original file paths, for lazy loading file_paths: Vec<&'a PathBuf>, /// The actual FileOwner, which has the full file available - owners: Vector>>, + owners: Vector>>>, } impl<'a, Q: QueryContext> FileRegistry<'a, Q> { - pub fn get_file_owner(&self, pointer: FilePtr) -> &'a FileOwner { + pub fn get_file_owner(&self, pointer: FilePtr) -> &'a FileOwner> { self.owners[pointer.file as usize][pointer.version as usize] } @@ -86,7 +86,7 @@ impl<'a, Q: QueryContext> FileRegistry<'a, Q> { } /// Load a file in - pub fn load_file(&mut self, pointer: &FilePtr, file: &'a FileOwner) { + pub fn load_file(&mut self, pointer: &FilePtr, file: &'a FileOwner>) { self.push_revision(pointer, file) } @@ -108,16 +108,16 @@ impl<'a, Q: QueryContext> FileRegistry<'a, Q> { } } - pub fn files(&self) -> &Vector>> { + pub fn files(&self) -> &Vector>>> { &self.owners } - pub fn push_revision(&mut self, pointer: &FilePtr, file: &'a FileOwner) { + pub fn push_revision(&mut self, pointer: &FilePtr, file: &'a FileOwner>) { self.version_count[pointer.file as usize] += 1; self.owners[pointer.file as usize].push_back(file) } - pub fn push_new_file(&mut self, file: &'a FileOwner) -> FilePtr { + pub fn push_new_file(&mut self, file: &'a FileOwner>) -> FilePtr { self.version_count.push(1); self.file_paths.push(&file.name); self.owners.push_back(vector![file]); diff --git a/crates/grit-util/src/ast_node.rs b/crates/grit-util/src/ast_node.rs index aadb8db74..40b596332 100644 --- a/crates/grit-util/src/ast_node.rs +++ b/crates/grit-util/src/ast_node.rs @@ -42,10 +42,6 @@ pub trait AstNode: std::fmt::Debug + Sized { /// Returns the code range of the node. fn code_range(&self) -> CodeRange; - /// Returns the full source code of the parse tree to which the node - /// belongs. - fn full_source(&self) -> &str; - /// Returns a cursor for traversing the tree, starting at the current node. fn walk(&self) -> impl AstCursor; } diff --git a/crates/grit-util/src/code_range.rs b/crates/grit-util/src/code_range.rs index bd920c07b..7d503c99d 100644 --- a/crates/grit-util/src/code_range.rs +++ b/crates/grit-util/src/code_range.rs @@ -31,4 +31,9 @@ impl CodeRange { let address = thin_ptr as usize; self.address == address } + + /// Returns whether the given index is contained within the range. + pub fn contains(&self, index: u32) -> bool { + self.start <= index && self.end > index + } } diff --git a/crates/grit-util/src/effect_kind.rs b/crates/grit-util/src/effect_kind.rs new file mode 100644 index 000000000..abcf065ad --- /dev/null +++ b/crates/grit-util/src/effect_kind.rs @@ -0,0 +1,5 @@ +#[derive(Debug, Clone)] +pub enum EffectKind { + Rewrite, + Insert, +} diff --git a/crates/grit-util/src/language.rs b/crates/grit-util/src/language.rs index 4623907a5..deffec21b 100644 --- a/crates/grit-util/src/language.rs +++ b/crates/grit-util/src/language.rs @@ -1,4 +1,6 @@ -use crate::{constants::*, traverse, AstNode, ByteRange, CodeRange, Order, Range}; +use std::borrow::Cow; + +use crate::{constants::*, ranges::EffectRange, AstNode, ByteRange, CodeRange, Range}; use regex::Regex; pub enum GritMetaValue { @@ -60,29 +62,22 @@ pub trait Language: Sized { Some(node.byte_range()) } - // in languages we pad such as python or yaml there are - // some kinds of nodes we don't want to pad, such as python strings. - // this function identifies those nodes. - #[allow(unused_variables)] - fn should_skip_padding(&self, node: &Self::Node<'_>) -> bool { - false - } - - #[allow(unused_variables)] - fn get_skip_padding_ranges_for_snippet(&self, snippet: &str) -> Vec { - Vec::new() - } + /// Removes the padding from every line in the snippet identified by the + /// given `range`, such that the first line of the snippet is left-aligned. + fn align_padding<'a>( + &self, + node: &Self::Node<'a>, + range: &CodeRange, + skip_ranges: &[CodeRange], + new_padding: Option, + offset: usize, + substitutions: &mut [(EffectRange, String)], + ) -> Cow<'a, str>; - #[allow(unused_variables)] - fn get_skip_padding_ranges(&self, node: &Self::Node<'_>) -> Vec { - let mut ranges = Vec::new(); - for n in traverse(node.walk(), Order::Pre) { - if self.should_skip_padding(&n) { - ranges.push(n.code_range()) - } - } - ranges - } + /// Pads `snippet` by applying the given `padding` to every line. + /// + /// Takes padding rules for whitespace-significant languages into account. + fn pad_snippet<'a>(&self, snippet: &'a str, padding: &str) -> Cow<'a, str>; fn substitute_metavariable_prefix(&self, src: &str) -> String { self.metavariable_regex() @@ -126,6 +121,8 @@ pub trait Language: Sized { } } + fn get_skip_padding_ranges(&self, node: &Self::Node<'_>) -> Vec; + /// Whether snippets should be padded. /// /// This is generally `true` for languages with relevant whitespace. diff --git a/crates/grit-util/src/lib.rs b/crates/grit-util/src/lib.rs index 1ebfa1241..868103b91 100644 --- a/crates/grit-util/src/lib.rs +++ b/crates/grit-util/src/lib.rs @@ -3,6 +3,7 @@ mod ast_node; mod ast_node_traversal; mod code_range; pub mod constants; +mod effect_kind; mod language; mod parser; mod position; @@ -12,10 +13,11 @@ pub use analysis_logs::{AnalysisLog, AnalysisLogBuilder, AnalysisLogs}; pub use ast_node::AstNode; pub use ast_node_traversal::{traverse, AstCursor, Order}; pub use code_range::CodeRange; +pub use effect_kind::EffectKind; pub use language::{GritMetaValue, Language, Replacement}; pub use parser::{Ast, FileOrigin, Parser, SnippetTree}; pub use position::Position; pub use ranges::{ - ByteRange, FileRange, InputRanges, MatchRanges, Range, RangeWithoutByte, UtilRange, - VariableBinding, VariableMatch, + ByteRange, EffectRange, FileRange, InputRanges, MatchRanges, Range, RangeWithoutByte, + UtilRange, VariableBinding, VariableMatch, }; diff --git a/crates/grit-util/src/parser.rs b/crates/grit-util/src/parser.rs index 4db2d674e..5925ac5e0 100644 --- a/crates/grit-util/src/parser.rs +++ b/crates/grit-util/src/parser.rs @@ -1,5 +1,5 @@ use crate::{AnalysisLogs, AstNode}; -use std::{marker::PhantomData, path::Path}; +use std::{borrow::Cow, marker::PhantomData, path::Path}; /// Information on where a file came from, for the parser to be smarter #[derive(Clone, Debug)] @@ -49,6 +49,9 @@ pub trait Ast: std::fmt::Debug + PartialEq + Sized { Self: 'a; fn root_node(&self) -> Self::Node<'_>; + + /// Returns the full source code of the tree. + fn source(&self) -> Cow; } #[derive(Clone, Debug)] diff --git a/crates/grit-util/src/ranges.rs b/crates/grit-util/src/ranges.rs index ad78ed0e2..d16889092 100644 --- a/crates/grit-util/src/ranges.rs +++ b/crates/grit-util/src/ranges.rs @@ -1,4 +1,4 @@ -use crate::Position; +use crate::{EffectKind, Position}; use serde::{Deserialize, Serialize}; use std::{ops::Add, path::PathBuf}; @@ -347,3 +347,28 @@ mod tests { assert_eq!(new_range, ByteRange::new(13, 15)); } } + +#[derive(Debug, Clone)] +pub struct EffectRange { + pub kind: EffectKind, + pub range: std::ops::Range, +} + +impl EffectRange { + pub fn new(kind: EffectKind, range: std::ops::Range) -> Self { + Self { kind, range } + } + + pub fn start(&self) -> usize { + self.range.start + } + + // The range which is actually edited by this effect + // This is used for most operations, but does not account for expansion from deleted commas + pub fn effective_range(&self) -> std::ops::Range { + match self.kind { + EffectKind::Rewrite => self.range.clone(), + EffectKind::Insert => self.range.end..self.range.end, + } + } +} diff --git a/crates/language/src/csharp.rs b/crates/language/src/csharp.rs index 9fa0eb0a5..a7176138e 100644 --- a/crates/language/src/csharp.rs +++ b/crates/language/src/csharp.rs @@ -52,7 +52,7 @@ impl NodeTypes for CSharp { } impl Language for CSharp { - type Node<'a> = NodeWithSource<'a>; + use_marzano_delegate!(); fn language_name(&self) -> &'static str { "CSharp" @@ -61,14 +61,6 @@ impl Language for CSharp { fn snippet_context_strings(&self) -> &[(&'static str, &'static str)] { &[("", "")] } - - fn is_comment(&self, node: &NodeWithSource) -> bool { - MarzanoLanguage::is_comment_node(self, node) - } - - fn is_metavariable(&self, node: &NodeWithSource) -> bool { - MarzanoLanguage::is_metavariable_node(self, node) - } } impl<'a> MarzanoLanguage<'a> for CSharp { diff --git a/crates/language/src/css.rs b/crates/language/src/css.rs index 0b169c391..fc7d47a2d 100644 --- a/crates/language/src/css.rs +++ b/crates/language/src/css.rs @@ -58,7 +58,7 @@ impl NodeTypes for Css { } impl Language for Css { - type Node<'a> = NodeWithSource<'a>; + use_marzano_delegate!(); fn language_name(&self) -> &'static str { "CSS" @@ -72,14 +72,6 @@ impl Language for Css { ] } - fn is_comment(&self, node: &NodeWithSource) -> bool { - MarzanoLanguage::is_comment_node(self, node) - } - - fn is_metavariable(&self, node: &NodeWithSource) -> bool { - MarzanoLanguage::is_metavariable_node(self, node) - } - fn make_single_line_comment(&self, text: &str) -> String { format!("/* {} */\n", text) } diff --git a/crates/language/src/go.rs b/crates/language/src/go.rs index 0729a21f4..ce71a963d 100644 --- a/crates/language/src/go.rs +++ b/crates/language/src/go.rs @@ -51,7 +51,7 @@ impl NodeTypes for Go { } impl Language for Go { - type Node<'a> = NodeWithSource<'a>; + use_marzano_delegate!(); fn language_name(&self) -> &'static str { "Go" @@ -66,14 +66,6 @@ impl Language for Go { ("func GRIT_FUNC(GRIT_ARG *GRIT_PACKAGE.", ") {}"), ] } - - fn is_comment(&self, node: &NodeWithSource) -> bool { - MarzanoLanguage::is_comment_node(self, node) - } - - fn is_metavariable(&self, node: &NodeWithSource) -> bool { - MarzanoLanguage::is_metavariable_node(self, node) - } } impl<'a> MarzanoLanguage<'a> for Go { diff --git a/crates/language/src/hcl.rs b/crates/language/src/hcl.rs index 48b369fdb..3c42ee2e6 100644 --- a/crates/language/src/hcl.rs +++ b/crates/language/src/hcl.rs @@ -51,7 +51,7 @@ impl NodeTypes for Hcl { } impl Language for Hcl { - type Node<'a> = NodeWithSource<'a>; + use_marzano_delegate!(); fn language_name(&self) -> &'static str { "HCL" @@ -61,14 +61,6 @@ impl Language for Hcl { &[("", ""), ("GRIT_VAL = ", ""), ("GRIT_ID = { ", " }")] } - fn is_comment(&self, node: &NodeWithSource) -> bool { - MarzanoLanguage::is_comment_node(self, node) - } - - fn is_metavariable(&self, node: &NodeWithSource) -> bool { - MarzanoLanguage::is_metavariable_node(self, node) - } - fn make_single_line_comment(&self, text: &str) -> String { format!("# {}\n", text) } diff --git a/crates/language/src/html.rs b/crates/language/src/html.rs index a6f01b912..5f10f3877 100644 --- a/crates/language/src/html.rs +++ b/crates/language/src/html.rs @@ -52,7 +52,7 @@ impl NodeTypes for Html { } impl Language for Html { - type Node<'a> = NodeWithSource<'a>; + use_marzano_delegate!(); fn language_name(&self) -> &'static str { "HTML" @@ -62,14 +62,6 @@ impl Language for Html { &[("", "")] } - fn is_comment(&self, node: &NodeWithSource) -> bool { - MarzanoLanguage::is_comment_node(self, node) - } - - fn is_metavariable(&self, node: &NodeWithSource) -> bool { - MarzanoLanguage::is_metavariable_node(self, node) - } - fn make_single_line_comment(&self, text: &str) -> String { format!("\n", text) } diff --git a/crates/language/src/java.rs b/crates/language/src/java.rs index 43c01b29a..3b71c2b3f 100644 --- a/crates/language/src/java.rs +++ b/crates/language/src/java.rs @@ -53,7 +53,7 @@ impl Java { } } impl Language for Java { - type Node<'a> = NodeWithSource<'a>; + use_marzano_delegate!(); fn language_name(&self) -> &'static str { "Java" @@ -68,14 +68,6 @@ impl Language for Java { ("class GRIT_CLASS { GRIT_FN(", ") {} }"), ] } - - fn is_comment(&self, node: &NodeWithSource) -> bool { - MarzanoLanguage::is_comment_node(self, node) - } - - fn is_metavariable(&self, node: &NodeWithSource) -> bool { - MarzanoLanguage::is_metavariable_node(self, node) - } } impl<'a> MarzanoLanguage<'a> for Java { diff --git a/crates/language/src/javascript.rs b/crates/language/src/javascript.rs index 10564ea95..da9742aaf 100644 --- a/crates/language/src/javascript.rs +++ b/crates/language/src/javascript.rs @@ -77,7 +77,7 @@ impl NodeTypes for JavaScript { } impl Language for JavaScript { - type Node<'a> = NodeWithSource<'a>; + use_marzano_js_like_delegate!(); fn language_name(&self) -> &'static str { "JavaScript" @@ -105,10 +105,6 @@ impl Language for JavaScript { ] } - fn is_comment(&self, node: &NodeWithSource) -> bool { - MarzanoLanguage::is_comment_node(self, node) - } - fn is_metavariable(&self, node: &NodeWithSource) -> bool { js_like_is_metavariable(node, self, &["template_content"]) } @@ -130,10 +126,6 @@ impl Language for JavaScript { None } } - - fn check_replacements(&self, n: NodeWithSource<'_>, orphan_ranges: &mut Vec) { - jslike_check_replacements(n, orphan_ranges) - } } impl<'a> MarzanoLanguage<'a> for JavaScript { diff --git a/crates/language/src/json.rs b/crates/language/src/json.rs index 12eb23659..318c73860 100644 --- a/crates/language/src/json.rs +++ b/crates/language/src/json.rs @@ -51,7 +51,7 @@ impl Json { } impl Language for Json { - type Node<'a> = NodeWithSource<'a>; + use_marzano_delegate!(); fn language_name(&self) -> &'static str { "JSON" @@ -60,14 +60,6 @@ impl Language for Json { fn snippet_context_strings(&self) -> &[(&'static str, &'static str)] { &[("", ""), ("{ ", " }")] } - - fn is_comment(&self, node: &NodeWithSource) -> bool { - MarzanoLanguage::is_comment_node(self, node) - } - - fn is_metavariable(&self, node: &NodeWithSource) -> bool { - MarzanoLanguage::is_metavariable_node(self, node) - } } impl<'a> MarzanoLanguage<'a> for Json { diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index c5263c759..d714e8d92 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -1,8 +1,8 @@ use anyhow::{Context, Result}; use enum_dispatch::enum_dispatch; use grit_util::{ - traverse, AnalysisLogBuilder, AnalysisLogs, Ast, AstNode, FileOrigin, Language, Order, Parser, - SnippetTree, + traverse, AnalysisLogBuilder, AnalysisLogs, Ast, AstNode, CodeRange, EffectRange, FileOrigin, + Language, Order, Parser, SnippetTree, }; use itertools::Itertools; use marzano_util::{cursor_wrapper::CursorWrapper, node_with_source::NodeWithSource}; @@ -213,6 +213,10 @@ impl Ast for Tree { fn root_node(&self) -> Self::Node<'_> { NodeWithSource::new(self.tree.root_node(), &self.source) } + + fn source(&self) -> Cow { + Cow::Borrowed(&self.source) + } } impl PartialEq for Tree { @@ -304,6 +308,99 @@ pub trait MarzanoLanguage<'a>: Language = NodeWithSource<'a>> + NodeTyp .collect() } + fn align_padding<'b>( + &self, + node: &NodeWithSource<'b>, + range: &CodeRange, + skip_ranges: &[CodeRange], + new_padding: Option, + offset: usize, + substitutions: &mut [(EffectRange, String)], + ) -> std::borrow::Cow<'b, str> { + let src = node.source; + let Some(new_padding) = new_padding else { + return src[range.start as usize..range.end as usize].into(); + }; + + let newline_index = src[0..range.start as usize].rfind('\n'); + let pad_strip_amount = if let Some(index) = newline_index { + src[index..range.start as usize] + .chars() + .take_while(|c| c.is_whitespace()) + .count() + - 1 + } else { + 0 + }; + + let mut result = String::new(); + let snippet = &src[range.start as usize..range.end as usize]; + let mut lines = snippet.split('\n'); + + // assumes codebase uses spaces for indentation + let delta: isize = (new_padding as isize) - (pad_strip_amount as isize); + let padding = " ".repeat(pad_strip_amount); + let new_padding = " ".repeat(new_padding); + result.push_str(lines.next().unwrap_or_default()); + for line in lines { + result.push('\n'); + + // Safety: Safe because lines are slices of `snippet`, which is a slice of `src`. + let index = unsafe { get_slice_byte_offset(src, line) }; + + if !skip_ranges.iter().any(|r| r.contains(index)) { + if line.trim().is_empty() { + adjust_ranges(substitutions, offset + result.len(), -(line.len() as isize)); + continue; + } + + adjust_ranges(substitutions, offset + result.len(), delta); + if let Some(stripped_line) = line.strip_prefix(&padding) { + result.push_str(&new_padding); + result.push_str(stripped_line); + } else { + result.push_str(line); + } + } else { + result.push_str(line) + } + } + + for (_, snippet) in substitutions.iter_mut() { + match MarzanoLanguage::pad_snippet(self, snippet, &new_padding) { + std::borrow::Cow::Owned(padded) => *snippet = padded, + std::borrow::Cow::Borrowed(_) => { + // a borrowed result implies the padding didn't change + } + } + } + + result.into() + } + + // in languages we pad such as python or yaml there are + // some kinds of nodes we don't want to pad, such as python strings. + // this function identifies those nodes. + #[allow(unused_variables)] + fn should_skip_padding(&self, node: &Self::Node<'a>) -> bool { + false + } + + fn get_skip_padding_ranges(&self, node: &Self::Node<'a>) -> Vec { + let mut ranges = Vec::new(); + for n in traverse(node.walk(), Order::Pre) { + if self.should_skip_padding(&n) { + ranges.push(n.code_range()) + } + } + ranges + } + + #[allow(unused_variables)] + fn get_skip_padding_ranges_for_snippet(&self, snippet: &str) -> Vec { + Vec::new() + } + /// Ordinarily, we want to match on all possible fields, including the absence of nodes within a field. /// e.g., `my_function()` should not match `my_function(arg)`. /// @@ -366,6 +463,41 @@ pub trait MarzanoLanguage<'a>: Language = NodeWithSource<'a>> + NodeTyp ) -> Result, String> { Ok(None) } + + fn pad_snippet<'b>(&self, snippet: &'b str, padding: &str) -> Cow<'b, str> { + if padding.is_empty() { + return snippet.into(); + } + + let mut lines = snippet.split('\n'); + let mut result = Cow::Borrowed(lines.next().unwrap_or_default()); + + // Add the rest of lines in the snippet with padding + let skip_ranges = self.get_skip_padding_ranges_for_snippet(snippet); + for line in lines { + // Safety: This is safe because `line` is a slice of `snippet`. + let index = unsafe { get_slice_byte_offset(snippet, line) }; + + let result = result.to_mut(); + result.push('\n'); + if !skip_ranges.iter().any(|r| r.contains(index)) { + result.push_str(padding) + } + result.push_str(line); + } + result + } +} + +fn adjust_ranges(substitutions: &mut [(EffectRange, String)], index: usize, delta: isize) { + for (EffectRange { range, .. }, _) in substitutions.iter_mut() { + if range.start >= index { + range.start = (range.start as isize + delta) as usize; + } + if range.end >= index { + range.end = (range.end as isize + delta) as usize; + } + } } fn file_parsing_error( @@ -404,6 +536,16 @@ fn file_parsing_error( Ok(errors.into()) } +/// Returns the byte offset of `sub` relative to `sup`. +/// +/// This function assumes `sub` starts at or after `sup`, since it cannot return +/// negative offsets. +/// +/// Safety: `sup` and `sub` must be slices of the same string. +unsafe fn get_slice_byte_offset(sup: &str, sub: &str) -> u32 { + sub.as_ptr().byte_offset_from(sup.as_ptr()).unsigned_abs() as u32 +} + pub fn nodes_from_indices(indices: &[SnippetTree]) -> Vec { indices .iter() diff --git a/crates/language/src/lib.rs b/crates/language/src/lib.rs index 88112e5ab..ba809abe2 100644 --- a/crates/language/src/lib.rs +++ b/crates/language/src/lib.rs @@ -1,3 +1,61 @@ +macro_rules! use_marzano_base_delegate { + () => { + type Node<'a> = NodeWithSource<'a>; + + fn align_padding<'a>( + &self, + node: &Self::Node<'a>, + range: &grit_util::CodeRange, + skip_ranges: &[grit_util::CodeRange], + new_padding: Option, + offset: usize, + substitutions: &mut [(grit_util::EffectRange, String)], + ) -> std::borrow::Cow<'a, str> { + MarzanoLanguage::align_padding( + self, + node, + range, + skip_ranges, + new_padding, + offset, + substitutions, + ) + } + + fn pad_snippet<'a>(&self, snippet: &'a str, padding: &str) -> std::borrow::Cow<'a, str> { + MarzanoLanguage::pad_snippet(self, snippet, padding) + } + + fn get_skip_padding_ranges(&self, node: &Self::Node<'_>) -> Vec { + MarzanoLanguage::get_skip_padding_ranges(self, node) + } + + fn is_comment(&self, node: &NodeWithSource) -> bool { + MarzanoLanguage::is_comment_node(self, node) + } + }; +} + +macro_rules! use_marzano_js_like_delegate { + () => { + use_marzano_base_delegate!(); + + fn check_replacements(&self, n: NodeWithSource<'_>, replacements: &mut Vec) { + jslike_check_replacements(n, replacements) + } + }; +} + +macro_rules! use_marzano_delegate { + () => { + use_marzano_base_delegate!(); + + fn is_metavariable(&self, node: &NodeWithSource) -> bool { + MarzanoLanguage::is_metavariable_node(self, node) + } + }; +} + pub mod csharp; pub mod css; pub mod foreign_language; diff --git a/crates/language/src/markdown_block.rs b/crates/language/src/markdown_block.rs index d2947da48..7b3208e34 100644 --- a/crates/language/src/markdown_block.rs +++ b/crates/language/src/markdown_block.rs @@ -50,7 +50,7 @@ impl MarkdownBlock { } impl Language for MarkdownBlock { - type Node<'a> = NodeWithSource<'a>; + use_marzano_delegate!(); fn language_name(&self) -> &'static str { "MarkdownBlock" @@ -60,14 +60,6 @@ impl Language for MarkdownBlock { &[("", "")] } - fn is_comment(&self, node: &NodeWithSource) -> bool { - MarzanoLanguage::is_comment_node(self, node) - } - - fn is_metavariable(&self, node: &NodeWithSource) -> bool { - MarzanoLanguage::is_metavariable_node(self, node) - } - fn make_single_line_comment(&self, text: &str) -> String { format!("\n", text) } diff --git a/crates/language/src/markdown_inline.rs b/crates/language/src/markdown_inline.rs index 9fd65ada5..1918c672b 100644 --- a/crates/language/src/markdown_inline.rs +++ b/crates/language/src/markdown_inline.rs @@ -50,7 +50,7 @@ impl NodeTypes for MarkdownInline { } impl Language for MarkdownInline { - type Node<'a> = NodeWithSource<'a>; + use_marzano_delegate!(); fn language_name(&self) -> &'static str { "MarkdownInline" @@ -60,14 +60,6 @@ impl Language for MarkdownInline { &[("", "")] } - fn is_comment(&self, node: &NodeWithSource) -> bool { - MarzanoLanguage::is_comment_node(self, node) - } - - fn is_metavariable(&self, node: &NodeWithSource) -> bool { - MarzanoLanguage::is_metavariable_node(self, node) - } - fn make_single_line_comment(&self, text: &str) -> String { format!("\n", text) } diff --git a/crates/language/src/php.rs b/crates/language/src/php.rs index 61cac90c1..3556d9b86 100644 --- a/crates/language/src/php.rs +++ b/crates/language/src/php.rs @@ -59,7 +59,7 @@ impl NodeTypes for Php { } impl Language for Php { - type Node<'a> = NodeWithSource<'a>; + use_marzano_delegate!(); fn language_name(&self) -> &'static str { "PhpWithHTML" @@ -73,14 +73,6 @@ impl Language for Php { "//" } - fn is_comment(&self, node: &NodeWithSource) -> bool { - MarzanoLanguage::is_comment_node(self, node) - } - - fn is_metavariable(&self, node: &NodeWithSource) -> bool { - MarzanoLanguage::is_metavariable_node(self, node) - } - fn metavariable_prefix(&self) -> &'static str { php_like_metavariable_prefix() } diff --git a/crates/language/src/php_only.rs b/crates/language/src/php_only.rs index d0aff4f56..4ee19ff87 100644 --- a/crates/language/src/php_only.rs +++ b/crates/language/src/php_only.rs @@ -60,7 +60,7 @@ impl NodeTypes for PhpOnly { } impl Language for PhpOnly { - type Node<'a> = NodeWithSource<'a>; + use_marzano_delegate!(); fn language_name(&self) -> &'static str { "PhpOnly" @@ -74,14 +74,6 @@ impl Language for PhpOnly { "//" } - fn is_comment(&self, node: &NodeWithSource) -> bool { - MarzanoLanguage::is_comment_node(self, node) - } - - fn is_metavariable(&self, node: &NodeWithSource) -> bool { - MarzanoLanguage::is_metavariable_node(self, node) - } - fn metavariable_prefix(&self) -> &'static str { php_like_metavariable_prefix() } diff --git a/crates/language/src/python.rs b/crates/language/src/python.rs index aa27a7938..ff710feb4 100644 --- a/crates/language/src/python.rs +++ b/crates/language/src/python.rs @@ -58,7 +58,7 @@ impl NodeTypes for Python { } impl Language for Python { - type Node<'a> = NodeWithSource<'a>; + use_marzano_delegate!(); fn language_name(&self) -> &'static str { "Python" @@ -77,14 +77,6 @@ impl Language for Python { "#" } - fn is_comment(&self, node: &NodeWithSource) -> bool { - MarzanoLanguage::is_comment_node(self, node) - } - - fn is_metavariable(&self, node: &NodeWithSource) -> bool { - MarzanoLanguage::is_metavariable_node(self, node) - } - fn check_replacements(&self, n: NodeWithSource<'_>, replacements: &mut Vec) { if n.node.is_error() { if n.text().is_ok_and(|t| t == "->") { @@ -152,17 +144,6 @@ impl Language for Python { true } - fn should_skip_padding(&self, node: &NodeWithSource<'_>) -> bool { - self.skip_padding_sorts.contains(&node.node.kind_id()) - } - - fn get_skip_padding_ranges_for_snippet(&self, snippet: &str) -> Vec { - let mut parser = self.get_parser(); - let snippet = parser.parse_snippet("", snippet, ""); - let root = snippet.tree.root_node(); - self.get_skip_padding_ranges(&root) - } - fn make_single_line_comment(&self, text: &str) -> String { format!("# {}\n", text) } @@ -184,6 +165,17 @@ impl<'a> MarzanoLanguage<'a> for Python { fn get_parser(&self) -> Box> { Box::new(MarzanoNotebookParser::new(self, "python")) } + + fn should_skip_padding(&self, node: &NodeWithSource<'_>) -> bool { + self.skip_padding_sorts.contains(&node.node.kind_id()) + } + + fn get_skip_padding_ranges_for_snippet(&self, snippet: &str) -> Vec { + let mut parser = self.get_parser(); + let snippet = parser.parse_snippet("", snippet, ""); + let root = snippet.tree.root_node(); + MarzanoLanguage::get_skip_padding_ranges(self, &root) + } } #[cfg(test)] diff --git a/crates/language/src/ruby.rs b/crates/language/src/ruby.rs index ccc9f58de..bc92fa3ee 100644 --- a/crates/language/src/ruby.rs +++ b/crates/language/src/ruby.rs @@ -63,7 +63,7 @@ lazy_static! { } impl Language for Ruby { - type Node<'a> = NodeWithSource<'a>; + use_marzano_delegate!(); fn language_name(&self) -> &'static str { "Ruby" @@ -82,14 +82,6 @@ impl Language for Ruby { "#" } - fn is_comment(&self, node: &NodeWithSource) -> bool { - MarzanoLanguage::is_comment_node(self, node) - } - - fn is_metavariable(&self, node: &NodeWithSource) -> bool { - MarzanoLanguage::is_metavariable_node(self, node) - } - fn metavariable_prefix(&self) -> &'static str { "^" } diff --git a/crates/language/src/rust.rs b/crates/language/src/rust.rs index 805ddffff..cbf6d971f 100644 --- a/crates/language/src/rust.rs +++ b/crates/language/src/rust.rs @@ -105,7 +105,7 @@ impl NodeTypes for Rust { } impl Language for Rust { - type Node<'a> = NodeWithSource<'a>; + use_marzano_delegate!(); fn language_name(&self) -> &'static str { "Rust" @@ -120,14 +120,6 @@ impl Language for Rust { ("fn GRIT_FN(GRIT_ARG:", ") { }"), ] } - - fn is_comment(&self, node: &NodeWithSource) -> bool { - MarzanoLanguage::is_comment_node(self, node) - } - - fn is_metavariable(&self, node: &NodeWithSource) -> bool { - MarzanoLanguage::is_metavariable_node(self, node) - } } impl<'a> MarzanoLanguage<'a> for Rust { diff --git a/crates/language/src/solidity.rs b/crates/language/src/solidity.rs index 1ddc7f46c..2b6bb8037 100644 --- a/crates/language/src/solidity.rs +++ b/crates/language/src/solidity.rs @@ -52,7 +52,7 @@ impl NodeTypes for Solidity { } impl Language for Solidity { - type Node<'a> = NodeWithSource<'a>; + use_marzano_delegate!(); fn language_name(&self) -> &'static str { "Solidity" @@ -65,14 +65,6 @@ impl Language for Solidity { ("function GRIT_FUNCTION() { ", "; }"), ] } - - fn is_comment(&self, node: &NodeWithSource) -> bool { - MarzanoLanguage::is_comment_node(self, node) - } - - fn is_metavariable(&self, node: &NodeWithSource) -> bool { - MarzanoLanguage::is_metavariable_node(self, node) - } } impl<'a> MarzanoLanguage<'a> for Solidity { diff --git a/crates/language/src/sql.rs b/crates/language/src/sql.rs index 839450f98..4418d4e5c 100644 --- a/crates/language/src/sql.rs +++ b/crates/language/src/sql.rs @@ -51,7 +51,7 @@ impl NodeTypes for Sql { } impl Language for Sql { - type Node<'a> = NodeWithSource<'a>; + use_marzano_delegate!(); fn language_name(&self) -> &'static str { "SQL" @@ -73,14 +73,6 @@ impl Language for Sql { ] } - fn is_comment(&self, node: &NodeWithSource) -> bool { - MarzanoLanguage::is_comment_node(self, node) - } - - fn is_metavariable(&self, node: &NodeWithSource) -> bool { - MarzanoLanguage::is_metavariable_node(self, node) - } - fn make_single_line_comment(&self, text: &str) -> String { format!("-- {}\n", text) } diff --git a/crates/language/src/target_language.rs b/crates/language/src/target_language.rs index 8003eae57..3dd0852db 100644 --- a/crates/language/src/target_language.rs +++ b/crates/language/src/target_language.rs @@ -498,21 +498,43 @@ macro_rules! generate_target_language { } } - fn should_pad_snippet(&self) -> bool { + fn align_padding<'a>( + &self, + node: &Self::Node<'a>, + range: &CodeRange, + skip_ranges: &[CodeRange], + new_padding: Option, + offset: usize, + substitutions: &mut [(grit_util::EffectRange, String)], + ) -> std::borrow::Cow<'a, str> { match self { - $(Self::$language(lang) => Language::should_pad_snippet(lang)),+ + $(Self::$language(lang) => Language::align_padding( + lang, + node, + range, + skip_ranges, + new_padding, + offset, + substitutions + )),+ } } - fn should_skip_padding(&self, node: &NodeWithSource<'_>) -> bool { + fn pad_snippet<'a>(&self, snippet: &'a str, padding: &str) -> std::borrow::Cow<'a, str> { match self { - $(Self::$language(lang) => Language::should_skip_padding(lang, node)),+ + $(Self::$language(lang) => Language::pad_snippet(lang, snippet, padding)),+ } } - fn get_skip_padding_ranges_for_snippet(&self, snippet: &str) -> Vec { + fn get_skip_padding_ranges(&self, node: &Self::Node<'_>) -> Vec { match self { - $(Self::$language(lang) => Language::get_skip_padding_ranges_for_snippet(lang, snippet)),+ + $(Self::$language(lang) => Language::get_skip_padding_ranges(lang, node)),+ + } + } + + fn should_pad_snippet(&self) -> bool { + match self { + $(Self::$language(lang) => Language::should_pad_snippet(lang)),+ } } diff --git a/crates/language/src/toml.rs b/crates/language/src/toml.rs index 7d56a9fca..e7524dd24 100644 --- a/crates/language/src/toml.rs +++ b/crates/language/src/toml.rs @@ -51,7 +51,7 @@ impl NodeTypes for Toml { } impl Language for Toml { - type Node<'a> = NodeWithSource<'a>; + use_marzano_delegate!(); fn language_name(&self) -> &'static str { "Toml" @@ -61,14 +61,6 @@ impl Language for Toml { &[("", "")] } - fn is_comment(&self, node: &NodeWithSource) -> bool { - MarzanoLanguage::is_comment_node(self, node) - } - - fn is_metavariable(&self, node: &NodeWithSource) -> bool { - MarzanoLanguage::is_metavariable_node(self, node) - } - fn make_single_line_comment(&self, text: &str) -> String { format!("# {}\n", text) } diff --git a/crates/language/src/tsx.rs b/crates/language/src/tsx.rs index d2da1279c..1b821ed36 100644 --- a/crates/language/src/tsx.rs +++ b/crates/language/src/tsx.rs @@ -76,7 +76,7 @@ impl NodeTypes for Tsx { } impl Language for Tsx { - type Node<'a> = NodeWithSource<'a>; + use_marzano_js_like_delegate!(); fn language_name(&self) -> &'static str { "TSX" @@ -105,10 +105,6 @@ impl Language for Tsx { ] } - fn is_comment(&self, node: &NodeWithSource) -> bool { - MarzanoLanguage::is_comment_node(self, node) - } - fn is_metavariable(&self, node: &NodeWithSource) -> bool { js_like_is_metavariable( node, @@ -134,10 +130,6 @@ impl Language for Tsx { None } } - - fn check_replacements(&self, n: NodeWithSource<'_>, orphan_ranges: &mut Vec) { - jslike_check_replacements(n, orphan_ranges) - } } impl<'a> MarzanoLanguage<'a> for Tsx { diff --git a/crates/language/src/typescript.rs b/crates/language/src/typescript.rs index 7e00adf59..90da6db31 100644 --- a/crates/language/src/typescript.rs +++ b/crates/language/src/typescript.rs @@ -72,7 +72,7 @@ impl NodeTypes for TypeScript { } impl Language for TypeScript { - type Node<'a> = NodeWithSource<'a>; + use_marzano_js_like_delegate!(); fn language_name(&self) -> &'static str { "TypeScript" @@ -101,10 +101,6 @@ impl Language for TypeScript { ] } - fn is_comment(&self, node: &NodeWithSource) -> bool { - MarzanoLanguage::is_comment_node(self, node) - } - fn is_metavariable(&self, node: &NodeWithSource) -> bool { js_like_is_metavariable( node, @@ -130,10 +126,6 @@ impl Language for TypeScript { None } } - - fn check_replacements(&self, n: NodeWithSource<'_>, replacements: &mut Vec) { - jslike_check_replacements(n, replacements) - } } impl<'a> MarzanoLanguage<'a> for TypeScript { diff --git a/crates/language/src/vue.rs b/crates/language/src/vue.rs index 893cb0172..1be70c107 100644 --- a/crates/language/src/vue.rs +++ b/crates/language/src/vue.rs @@ -53,7 +53,7 @@ impl NodeTypes for Vue { } impl Language for Vue { - type Node<'a> = NodeWithSource<'a>; + use_marzano_delegate!(); fn language_name(&self) -> &'static str { "Vue" @@ -63,14 +63,6 @@ impl Language for Vue { &[("", "")] } - fn is_comment(&self, node: &NodeWithSource) -> bool { - MarzanoLanguage::is_comment_node(self, node) - } - - fn is_metavariable(&self, node: &NodeWithSource) -> bool { - MarzanoLanguage::is_metavariable_node(self, node) - } - fn make_single_line_comment(&self, text: &str) -> String { format!("\n", text) } diff --git a/crates/language/src/yaml.rs b/crates/language/src/yaml.rs index 0f6a3357d..007f367a7 100644 --- a/crates/language/src/yaml.rs +++ b/crates/language/src/yaml.rs @@ -69,7 +69,7 @@ impl NodeTypes for Yaml { } impl Language for Yaml { - type Node<'a> = NodeWithSource<'a>; + use_marzano_delegate!(); fn language_name(&self) -> &'static str { "YAML" @@ -83,14 +83,6 @@ impl Language for Yaml { "#" } - fn is_comment(&self, node: &NodeWithSource) -> bool { - MarzanoLanguage::is_comment_node(self, node) - } - - fn is_metavariable(&self, node: &NodeWithSource) -> bool { - MarzanoLanguage::is_metavariable_node(self, node) - } - // Given a character, return the character that should be used to pad the snippet (if any) fn take_padding(&self, current: char, next: Option) -> Option { if current.is_whitespace() { diff --git a/crates/util/src/node_with_source.rs b/crates/util/src/node_with_source.rs index 55b3dbad9..6e92a1c69 100644 --- a/crates/util/src/node_with_source.rs +++ b/crates/util/src/node_with_source.rs @@ -142,10 +142,6 @@ impl<'a> AstNode for NodeWithSource<'a> { CodeRange::new(self.node.start_byte(), self.node.end_byte(), self.source) } - fn full_source(&self) -> &str { - self.source - } - fn walk(&self) -> impl AstCursor { CursorWrapper::new(self.node.walk(), self.source) }