1use std::{
2 fs::{read_to_string, File},
3 io::Write,
4};
5
6use ignore::Walk;
7use swc_core::{
8 common::{sync::Lrc, FileName, SourceMap},
9 ecma::{
10 ast::CallExpr,
11 codegen::{text_writer::JsWriter, Emitter},
12 parser::{lexer::Lexer, Parser, StringInput},
13 visit::{Visit, VisitWith},
14 },
15};
16use tree_sitter::{Parser as TParser, Query, QueryCursor};
17
18pub fn build_ts_map() {
19 let mut vue = VueTs::default();
20 let mut ts_content = vec![];
21 let mut ntw_path = String::new();
22 for result in Walk::new("./") {
23 match result {
24 Ok(entry) => {
25 let path = entry.path().display().to_string();
26
27 if path.ends_with("not-tailwind.ts") {
28 ntw_path = path.clone();
29 }
30
31 if path.ends_with(".vue") || path.ends_with(".ts") {
32 let content = read_to_string(&path).unwrap();
33 ts_content.extend(get_ts_content(&path, content, &mut vue));
34 }
35 }
36 Err(err) => println!("ERROR: {}", err),
37 }
38 }
39 if ntw_path == "" {
40 println!("not-tailwind.ts not found");
41 ntw_path = "not-tailwind.ts".to_string();
42 }
43
44 let start = "export const ntw = new Map();// as Map<NtwType, string>;\n"
45 .to_string();
46
47 let mut setters = "".to_string();
48 let mut types = "type NtwType = ".to_string();
49 let mut added_type = false;
50 ts_content.dedup();
51 for i in &ts_content {
52 let content = format!("ntw.set('{}', '{}');\n", i, i);
53 setters.push_str(&content);
54 types.clear();
56 added_type = true;
57 }
58 if !added_type {
59 types.push_str(" '' ");
60 }
61 types.push_str(";");
62 let ntw = format!("{types}\n{start}\n{setters}");
63 let mut file = File::create(ntw_path).unwrap();
64 file.write_all(ntw.as_bytes()).ok();
65}
66
67pub struct VueTs {
68 pub parser: TParser,
69 pub query: Query,
70}
71
72impl Clone for VueTs {
73 fn clone(&self) -> Self {
74 Self::default()
75 }
76}
77
78impl Default for VueTs {
79 fn default() -> VueTs {
80 let mut parser = TParser::new();
81 parser.set_language(tree_sitter_vue::language()).unwrap();
82
83 let query = r#"
84
85(
86(script_element
87 (raw_text) @text)
88@script)
89
90 "#;
91
92 let query = Query::new(tree_sitter_vue::language(), query).unwrap();
93 Self { parser, query }
94 }
95}
96
97pub fn get_ts_content(
98 file_name: &str,
99 mut content: String,
100 vue: &mut VueTs,
101) -> Vec<String> {
102 if file_name.ends_with(".vue") {
103 let tree = vue.parser.parse(&content, None).unwrap();
104
105 let mut cursor = QueryCursor::new();
106 let capture_names = vue.query.capture_names();
107 let node = tree.root_node();
108 let c2 = content.clone();
109 let captures = cursor.captures(&vue.query, node, c2.as_bytes());
110 let mut new_content = String::new();
111 let mut id = 0;
112 for m in captures {
113 for capture in m.0.captures {
114 let name = &capture_names[capture.index as usize];
115 if name == "text" {
116 if id == capture.node.id() {
117 continue;
118 }
119 id = capture.node.id();
120 if let Ok(content) =
121 capture.node.utf8_text(&content.as_bytes())
122 {
123 new_content.push_str(&content);
124 }
125 }
126 }
127 }
128 content = new_content;
129 }
130 check_script(&content)
131}
132
133#[derive(Default)]
134pub struct ScriptContent {
135 ntw_import: bool,
136 is_import: bool,
137 identifiers: Vec<String>,
138}
139
140impl ScriptContent {
141 pub fn check_expr(&mut self, node: &CallExpr) -> Option<String> {
142 let expr = node.clone().callee.expr()?;
143 let member = expr.member()?;
144 let object = member.obj.ident()?;
145 if object.sym != "ntw" {
146 return None;
147 }
148 let prop = member.prop.ident()?;
149 if prop.sym != "get" {
150 return None;
151 }
152 let first_arg = node.args.first()?;
153 let lit = first_arg.clone().expr.lit()?;
154 let value = lit.str()?.value;
155 Some(value.to_string())
156 }
157}
158
159impl Visit for ScriptContent {
160 fn visit_import_decl(&mut self, node: &swc_core::ecma::ast::ImportDecl) {
161 if self.ntw_import {
162 return;
163 }
164 for specifier in &node.specifiers {
165 if !specifier.is_named() {
166 return;
167 }
168 if let Some(named) = &specifier.clone().named() {
169 if named.local.sym == "ntw" {
170 self.is_import = true;
171 break;
172 }
173 }
174 }
175 if self.is_import {
176 if node.src.value.ends_with("not-tailwind") {
177 self.ntw_import = true;
178 } else {
179 self.ntw_import = false;
180 self.ntw_import = false;
181 }
182 }
183 }
184
185 fn visit_call_expr(&mut self, node: &CallExpr) {
186 if self.ntw_import == false {
187 return;
188 }
189 let id = self.check_expr(node);
190 if let Some(id) = id {
191 if self.identifiers.contains(&id) {
192 return;
193 }
194 self.identifiers.push(id);
195 } else {
196 <CallExpr as VisitWith<Self>>::visit_children_with(node, self);
197 }
198 }
199}
200
201pub fn check_script(old_content: &str) -> Vec<String> {
202 let cm: Lrc<SourceMap> = Default::default();
203 let fm = cm.new_source_file(
204 Lrc::new(FileName::Custom("test.js".into())),
205 old_content.into(),
206 );
207 let lexer = Lexer::new(
208 swc_core::ecma::parser::Syntax::Typescript(Default::default()),
209 swc_core::ecma::ast::EsVersion::Es2023,
210 StringInput::from(&*fm),
211 None,
212 );
213 let mut parser = Parser::new_from(lexer);
214 let s = parser.parse_module();
215 let mut code = vec![];
216 let mut srcmap = vec![];
217 let mut map_visitor = ScriptContent::default();
218
219 match s {
220 Ok(module) => {
221 module.visit_with(&mut map_visitor);
222
223 {
224 let mut _emmiter = Emitter {
225 cfg: Default::default(),
226 cm: cm.clone(),
227 comments: None,
228 wr: JsWriter::new(
229 cm.clone(),
230 "\n",
231 &mut code,
232 Some(&mut srcmap),
233 ),
234 };
235 return map_visitor.identifiers;
236 }
239 }
240 Err(e) => {
241 println!("error 1, {:?}", e);
242 return map_visitor.identifiers;
243 }
244 }
245}