Skip to content

Commit

Permalink
feat: use grit pattern to find and replace grit code in yaml
Browse files Browse the repository at this point in the history
  • Loading branch information
Arian8j2 committed Jan 10, 2025
1 parent f593bdd commit 9e7c24b
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 25 deletions.
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion crates/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ indicatif = { version = "0.17.5" }
# Do *NOT* upgrade beyond 1.0.171 until https://github.com/serde-rs/serde/issues/2538 is fixed
serde = { version = "1.0.217", features = ["derive"] }
serde_json = { version = "1.0.96" }
serde_yaml = { version = "0.9.25" }
uuid = { version = "1.1", features = ["v4", "serde"] }
tokio = { version = "1", features = ["full"] }
chrono = { version = "0.4.26", features = ["serde"] }
Expand Down
104 changes: 82 additions & 22 deletions crates/cli/src/commands/format.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
use crate::{
resolver::{resolve_from_cwd, Source},
resolver::{resolve_from_cwd, GritModuleResolver, Source},
ux::{format_diff, DiffString},
};
use anyhow::{anyhow, ensure, Context, Result};
use anyhow::{anyhow, bail, ensure, Context, Result};
use biome_grit_formatter::context::GritFormatOptions;
use clap::Args;
use colored::Colorize;
use marzano_core::api::MatchResult;
use marzano_gritmodule::{config::ResolvedGritDefinition, parser::PatternFileExt};
use marzano_util::{rich_path::RichFile, runtime::ExecutionContext};
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use serde::Serialize;
use std::collections::BTreeMap;

#[derive(Args, Debug, Serialize, Clone)]
pub struct FormatArgs {
Expand Down Expand Up @@ -78,7 +81,7 @@ fn format_file_resolved_patterns(
let old_file_content = &first_pattern_raw_data.content;

let new_file_content = match first_pattern_raw_data.format {
PatternFileExt::Yaml => format_yaml_file(old_file_content)?,
PatternFileExt::Yaml => format_yaml_file(patterns.clone(), old_file_content)?,
PatternFileExt::Grit => format_grit_code(old_file_content)?,
PatternFileExt::Md => {
let hunks = patterns
Expand All @@ -101,27 +104,84 @@ fn format_file_resolved_patterns(
}
}

fn format_yaml_file(file_content: &str) -> Result<String> {
// deserializing manually and not using `SerializedGritConfig` because
// i don't want to remove any fields that `SerializedGritConfig` don't have such as 'version'
let mut config: serde_yaml::Value =
serde_yaml::from_str(file_content).with_context(|| "couldn't parse yaml file")?;
let patterns = config
.get_mut("patterns")
.ok_or_else(|| anyhow!("couldn't find patterns in yaml file"))?
.as_sequence_mut()
.ok_or_else(|| anyhow!("patterns in yaml file are not sequence"))?;
for pattern in patterns {
let Some(body) = pattern.get_mut("body") else {
continue;
};
if let serde_yaml::Value::String(body_str) = body {
*body_str = format_grit_code(body_str)?;
// extra new line at end of grit body looks more readable
body_str.push('\n');
/// bubble clause that finds a grit pattern with name "<pattern_name>" in yaml and
/// replaces it's body to new_body, `format_yaml_file` uses this pattern to replace
/// pattern bodies with formatted ones
const YAML_REPLACE_BODY_PATERN: &'static str = r#"
bubble file($body) where {
$body <: contains block_mapping(items=$items) where {
$items <: within `patterns: $_`,
$items <: contains `name: $name`,
$name <: "<pattern_name>",
$items <: contains `body: $yaml_body`,
$new_body = "<new_body>",
$yaml_body => $new_body
},
}
"#;

/// format each pattern and use gritql pattern to match and rewrite
fn format_yaml_file(patterns: Vec<ResolvedGritDefinition>, file_content: &str) -> Result<String> {
let bubbles = patterns
.iter()
.map(|pattern| {
let formatted_body = format_grit_code(&pattern.body)
.with_context(|| format!("could not format '{}'", pattern.name()))?;
let bubble = YAML_REPLACE_BODY_PATERN
.replace("<pattern_name>", pattern.name())
.replace("<new_body>", &format_yaml_body_code(&formatted_body));
Ok(bubble)
})
.collect::<Result<Vec<_>>>()?
.join(",\n");
let pattern_body = format!("language yaml\nsequential{{ {bubbles} }}");
apply_grit_rewrite(file_content, &pattern_body)
}

fn format_yaml_body_code(input: &str) -> String {
// yaml body still needs two indentation to look good
let body_with_prefix = prefix_lines(&input, &" ".repeat(2));
let escaped_body = body_with_prefix.replace("\"", "\\\"");
// body: |
// escaped_body
format!("|\n{escaped_body}")
}

fn prefix_lines(input: &str, prefix: &str) -> String {
input
.lines()
.map(|line| {
if line.is_empty() {
line.to_owned()
} else {
format!("{prefix}{line}")
}
})
.collect::<Vec<_>>()
.join("\n")
}

fn apply_grit_rewrite(input: &str, pattern: &str) -> Result<String> {
let resolver = GritModuleResolver::new();
let rich_pattern = resolver.make_pattern(&pattern, None)?;

let compiled = rich_pattern
.compile(&BTreeMap::new(), None, None, None)
.map(|cr| cr.problem)
.with_context(|| "could not compile pattern")?;

let rich_file = RichFile::new(String::new(), input.to_owned());
let runtime = ExecutionContext::default();
for result in compiled.execute_file(&rich_file, &runtime) {
if let MatchResult::Rewrite(rewrite) = result {
let content = rewrite
.rewritten
.content
.ok_or_else(|| anyhow!("rewritten content is empty"))?;
return Ok(content);
}
}
Ok(serde_yaml::to_string(&config)?)
bail!("no rewrite result after applying grit pattern")
}

fn format_pattern_as_hunk_changes(pattern: &ResolvedGritDefinition) -> Result<HunkChange> {
Expand Down
9 changes: 9 additions & 0 deletions crates/cli_bin/fixtures/unformatted_patterns/.grit/grit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,12 @@ patterns:
}
- file: ./others/test_move_import.md

- name: some_json_pattern
body: |
language json
`account: $val` where {
$val <: contains `password: $password`,
$password => raw`hidden`
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ source: crates/cli_bin/tests/format.rs
expression: "vec![yaml_file_content, test_move_import_file_content,\naspect_ratio_md_file_content, dependency_grit_file_content]"
snapshot_kind: text
---
- "version: 0.0.1\npatterns:\n- name: aspect_ratio_yaml\n description: Yaml version of aspect_ratio.md\n body: |+\n language css;\n `a { $props }` where { $props <: contains `aspect-ratio: $x` }\n\n- file: ./others/test_move_import.md\n"
- "version: 0.0.1\npatterns:\n - name: aspect_ratio_yaml\n description: Yaml version of aspect_ratio.md\n body: |\n language css;\n `a { $props }` where { $props <: contains `aspect-ratio: $x` }\n\n - file: ./others/test_move_import.md\n\n - name: some_json_pattern\n body: |\n language json;\n `account: $val` where {\n \t$val <: contains `password: $password`,\n \t$password => raw`hidden`\n }"
- "---\nprivate: true\ntags: [private]\n---\n```grit\nlanguage js;\n`sanitizeFilePath` as $s where {\n\tmove_import(`sanitizeFilePath`, `'@getgrit/universal'`)\n}\n```\n"
- "---\ntitle: Aspect ratio\n---\n\n```grit\nlanguage css;\n`a { $props }` where { $props <: contains `aspect-ratio: $x` }\n```\n\n## Matches the right selector and declaration block\n\n```css\na {\n width: calc(100% - 80px);\n aspect-ratio: 1/2;\n font-size: calc(10px + (56 - 10) * ((100vw - 320px) / (1920 - 320)));\n}\n\n#some-id {\n some-property: 5px;\n}\n\na.b ~ c.d {\n}\n.e.f + .g.h {\n}\n\n@font-face {\n font-family: 'Open Sans';\n src: url('/a') format('woff2'), url('/b/c') format('woff');\n}\n```\n\n```css\na {\n width: calc(100% - 80px);\n aspect-ratio: 1/2;\n font-size: calc(10px + (56 - 10) * ((100vw - 320px) / (1920 - 320)));\n}\n\n#some-id {\n some-property: 5px;\n}\n\na.b ~ c.d {\n}\n.e.f + .g.h {\n}\n\n@font-face {\n font-family: 'Open Sans';\n src: url('/a') format('woff2'), url('/b/c') format('woff');\n}\n```\n"
- "language json;\npattern upgrade_dependency($target_dep, $target_version, $dependency_key) {\n\tor {\n\t\t`$key: $value` where {\n\t\t\t$key <: `\"$target_dep\"`,\n\t\t\t$value => `\"$target_version\"`\n\t\t},\n\t\tpair($key, $value) where {\n\t\t\t$key <: `\"$dependency_key\"`,\n\t\t\t$value <: object($properties) where {\n\t\t\t\t$properties <: notcontains pair(key = $dep_key) where {\n\t\t\t\t\t$dep_key <: contains `$target_dep`\n\t\t\t\t},\n\t\t\t\t$properties => `\"$target_dep\": \"$target_version\",\\n$properties`\n\t\t\t}\n\t\t}\n\t}}\n\npattern console_method_to_info($method) {\n\t`console.$method($message)` => `console.info($message)`}\n"

0 comments on commit 9e7c24b

Please sign in to comment.