not_tailwind/
read_vue.rs

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.push_str(&format!("| '{}' ", i));
55        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                // if emitter.emit_module(&module).is_ok() {
237                // }
238            }
239        }
240        Err(e) => {
241            println!("error 1, {:?}", e);
242            return map_visitor.identifiers;
243        }
244    }
245}