-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmod.rs
More file actions
190 lines (170 loc) · 5.8 KB
/
mod.rs
File metadata and controls
190 lines (170 loc) · 5.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
// SPDX-FileCopyrightText: 2022 Herrington Darkholme <2883231+HerringtonDarkholme@users.noreply.github.com>
// SPDX-FileCopyrightText: 2025 Knitli Inc. <knitli@knit.li>
// SPDX-FileContributor: Adam Poulemanos <adam@knit.li>
//
// SPDX-License-Identifier: AGPL-3.0-or-later AND MIT
mod parse;
mod rewrite;
mod string_case;
mod trans;
use crate::rule::referent_rule::ReferentRuleError;
use crate::{DeserializeEnv, RuleCore};
use thread_ast_engine::Doc;
use thread_ast_engine::Language;
use thread_ast_engine::meta_var::MetaVarEnv;
use thread_ast_engine::meta_var::MetaVariable;
use parse::ParseTransError;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use thread_utilities::RapidMap;
pub use trans::Trans;
#[derive(Serialize, Deserialize, Clone, JsonSchema, Debug)]
#[serde(untagged)]
pub enum Transformation {
Simplified(String),
Object(Trans<String>),
}
impl Transformation {
pub fn parse<L: Language>(&self, lang: &L) -> Result<Trans<MetaVariable>, TransformError> {
match self {
Transformation::Simplified(s) => {
let t: Trans<String> = s.parse()?;
t.parse(lang)
}
Transformation::Object(t) => t.parse(lang),
}
}
}
#[derive(Error, Debug)]
pub enum TransformError {
#[error("Cannot parse transform string.")]
Parse(#[from] ParseTransError),
#[error("`{0}` has a cyclic dependency.")]
Cyclic(String),
#[error("Transform var `{0}` has already defined.")]
AlreadyDefined(String),
#[error("source `{0}` should be $-prefixed.")]
MalformedVar(String),
}
#[derive(Clone, Debug)]
pub struct Transform {
transforms: Vec<(String, Trans<MetaVariable>)>,
}
impl Transform {
pub fn deserialize<L: Language>(
map: &RapidMap<String, Transformation>,
env: &DeserializeEnv<L>,
) -> Result<Self, TransformError> {
let map: Result<_, _> = map
.iter()
.map(|(key, val)| val.parse(&env.lang).map(|t| (key.to_string(), t)))
.collect();
let map = map?;
let order = env.get_transform_order(&map).map_err(|e| match e {
ReferentRuleError::CyclicRule(s) => TransformError::Cyclic(s),
_ => unreachable!(
"get_transform_order uses None for env, so only CyclicRule is possible"
),
})?;
let transforms = order
.iter()
.map(|&key| (key.to_string(), map[key].clone()))
.collect();
Ok(Self { transforms })
}
pub fn apply_transform<'c, D: Doc>(
&self,
env: &mut MetaVarEnv<'c, D>,
rewriters: &RapidMap<String, RuleCore>,
enclosing_env: &MetaVarEnv<'c, D>,
) {
let mut ctx = Ctx {
env,
rewriters,
enclosing_env,
};
for (key, tr) in &self.transforms {
tr.insert(key, &mut ctx);
}
}
pub(crate) fn keys(&self) -> impl Iterator<Item = &String> {
self.transforms.iter().map(|t| &t.0)
}
pub(crate) fn values(&self) -> impl Iterator<Item = &Trans<MetaVariable>> {
self.transforms.iter().map(|t| &t.1)
}
}
// two lifetime to represent env root lifetime and lang/trans lifetime
struct Ctx<'b, 'c, D: Doc> {
rewriters: &'b RapidMap<String, RuleCore>,
env: &'b mut MetaVarEnv<'c, D>,
enclosing_env: &'b MetaVarEnv<'c, D>,
}
#[cfg(test)]
mod test {
use super::*;
use crate::from_str;
use crate::test::TypeScript;
use thread_ast_engine::tree_sitter::LanguageExt;
#[test]
fn test_transform_str() {}
#[test]
fn test_single_cyclic_transform() {
let mut trans = RapidMap::default();
let trans_a = from_str("substring: {source: $A}").unwrap();
trans.insert("A".into(), trans_a);
let env = DeserializeEnv::new(TypeScript::Tsx);
match Transform::deserialize(&trans, &env) {
Err(TransformError::Cyclic(a)) => assert_eq!(a, "A"),
_ => panic!("unexpected error"),
}
}
#[test]
fn test_cyclic_transform() {
let mut trans = RapidMap::default();
let trans_a = from_str("substring: {source: $B}").unwrap();
trans.insert("A".into(), trans_a);
let trans_b = from_str("substring: {source: $A}").unwrap();
trans.insert("B".into(), trans_b);
let env = DeserializeEnv::new(TypeScript::Tsx);
let ret = Transform::deserialize(&trans, &env);
assert!(matches!(ret, Err(TransformError::Cyclic(_))));
}
#[test]
fn test_transform_use_matched() {
let mut trans = RapidMap::default();
let trans_a = from_str("substring: {source: $C}").unwrap();
trans.insert("A".into(), trans_a);
let trans_b = from_str("substring: {source: $A}").unwrap();
trans.insert("B".into(), trans_b);
let env = DeserializeEnv::new(TypeScript::Tsx);
let ret = Transform::deserialize(&trans, &env);
assert!(ret.is_ok());
}
#[test]
fn test_transform_indentation() {
let src = "
if (true) {
let a = {
b: 123
}
}
";
let expected = "{
b: 123
}";
let mut trans = RapidMap::default();
let tr = from_str("{ substring: { source: $A } }").expect("should work");
trans.insert("TR".into(), tr);
let grep = TypeScript::Tsx.ast_grep(src);
let root = grep.root();
let mut nm = root.find("let a = $A").expect("should find");
let env = DeserializeEnv::new(TypeScript::Tsx);
let trans = Transform::deserialize(&trans, &env).expect("should deserialize");
trans.apply_transform(nm.get_env_mut(), &Default::default(), &Default::default());
let actual = nm.get_env().get_transformed("TR").expect("should have TR");
let actual = std::str::from_utf8(actual).expect("should work");
assert_eq!(actual, expected);
}
}