Skip to content

Commit 1803c9c

Browse files
added undefined classes check
1 parent 48025d3 commit 1803c9c

File tree

2 files changed

+80
-15
lines changed

2 files changed

+80
-15
lines changed

src/main.rs

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::{
88
use anyhow::Result;
99
use config::get_compiler_options;
1010
use css_parser::{extract_classes, ClassName};
11-
use tsx_parser::{extract_default_css_imports, extract_used_classes};
11+
use tsx_parser::{extract_default_css_imports, extract_used_classes, UsedClassName};
1212
use utils::{process_relative_import, replace_aliases};
1313

1414
mod config;
@@ -71,7 +71,7 @@ fn main() -> Result<()> {
7171

7272
let dir = list_files_in_directory(Path::new(".").to_path_buf(), tsconfig.exclude);
7373

74-
let mut used_classnames: HashMap<String, HashSet<String>> = Default::default();
74+
let mut used_classnames: HashMap<String, HashSet<UsedClassName>> = Default::default();
7575
let mut defined_classnames: HashMap<String, HashSet<ClassName>> = Default::default();
7676

7777
for entry in &dir {
@@ -85,7 +85,7 @@ fn main() -> Result<()> {
8585
process_relative_import(Path::new(entry), &mut style_path)?;
8686
replace_aliases(&mut style_path, tsconfig.compiler_options.paths.clone());
8787

88-
let used_fields = extract_used_classes(&code, &class_names);
88+
let used_fields = extract_used_classes(&code, &class_names, path.clone());
8989
used_classnames
9090
.entry(style_path)
9191
.or_insert_with(HashSet::new)
@@ -104,20 +104,22 @@ fn main() -> Result<()> {
104104
let mut files_count = 0;
105105
let mut errors_count = 0;
106106

107-
for (css_file, mut classes) in defined_classnames {
107+
for (css_file, mut classes_tsx) in defined_classnames.clone() {
108108
if let Some(used_css) = used_classnames.get(&css_file) {
109-
classes.retain(|v| !used_css.contains(&v.class_name));
109+
let used_css_flatten: Vec<String> =
110+
used_css.iter().map(|v| v.class_name.clone()).collect();
111+
classes_tsx.retain(|v| !used_css_flatten.contains(&v.class_name));
110112
}
111113

112-
if classes.is_empty() {
114+
if classes_tsx.is_empty() {
113115
continue;
114116
}
115117

116118
files_count += 1;
117-
errors_count += classes.len();
119+
errors_count += classes_tsx.len();
118120

119121
println!("{}{}{}", COLOR_BLUE, css_file, COLOR_RESET);
120-
for extra in classes {
122+
for extra in classes_tsx {
121123
println!(
122124
"{}{}:{} {}Warn{}: Unused class `{}` found",
123125
COLOR_YELLOW,
@@ -132,6 +134,47 @@ fn main() -> Result<()> {
132134
println!();
133135
}
134136

137+
let mut undefined_classes: HashMap<String, HashSet<UsedClassName>> = HashMap::new();
138+
139+
for (tsx_file, mut classes) in used_classnames {
140+
if let Some(defined_css) = defined_classnames.get(&tsx_file) {
141+
let defined_css: HashSet<String> =
142+
defined_css.iter().map(|v| v.class_name.clone()).collect();
143+
classes.retain(|v| !defined_css.contains(v.class_name.as_str()));
144+
}
145+
146+
if classes.is_empty() {
147+
continue;
148+
}
149+
150+
files_count += 1;
151+
errors_count += classes.len();
152+
153+
for extra in classes {
154+
undefined_classes
155+
.entry(extra.file_name.clone())
156+
.or_insert_with(HashSet::new)
157+
.insert(extra);
158+
}
159+
}
160+
161+
for undefined in undefined_classes {
162+
println!("{}{}{}", COLOR_BLUE, undefined.0, COLOR_RESET);
163+
for extra in undefined.1 {
164+
println!(
165+
"{}{}:{} {}Warn{}: Undefined class `{}` used",
166+
COLOR_YELLOW,
167+
extra.line,
168+
extra.column + 1,
169+
COLOR_YELLOW,
170+
COLOR_RESET,
171+
extra.class_name
172+
);
173+
}
174+
175+
println!();
176+
}
177+
135178
if errors_count == 0 {
136179
println!("{}✔{} No CSS lint warnings found", COLOR_GREEN, COLOR_RESET);
137180
} else {

src/tsx_parser.rs

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use swc_ecma_ast::{ImportSpecifier, Module, ModuleDecl};
44
use swc_ecma_parser::{Parser, StringInput, Syntax, TsSyntax};
55
use swc_ecma_visit::{Visit, VisitWith};
66

7-
pub fn module_parser(tsx_code: &str) -> Module {
7+
pub fn module_parser(tsx_code: &str) -> (Module, Lrc<SourceMap>) {
88
let cm: Lrc<SourceMap> = Default::default();
99
let fm = cm.new_source_file(FileName::Custom("input.tsx".into()).into(), tsx_code.into());
1010

@@ -18,20 +18,36 @@ pub fn module_parser(tsx_code: &str) -> Module {
1818

1919
let mut parser = Parser::new(syntax, StringInput::from(&*fm), None);
2020

21-
parser.parse_module().expect("Failed to parse")
21+
(parser.parse_module().expect("Failed to parse"), cm)
22+
}
23+
24+
#[derive(Eq, PartialEq, Hash, Debug)]
25+
pub struct UsedClassName {
26+
pub class_name: String,
27+
pub file_name: String,
28+
pub line: usize,
29+
pub column: usize,
2230
}
2331

2432
struct PropertyFinder {
2533
variable_name: String,
26-
properties: HashSet<String>,
34+
file_name: String,
35+
properties: HashSet<UsedClassName>,
36+
source_map: Lrc<SourceMap>,
2737
}
2838

2939
impl Visit for PropertyFinder {
3040
fn visit_member_expr(&mut self, node: &swc_ecma_ast::MemberExpr) {
3141
if let swc_ecma_ast::Expr::Ident(ref obj) = *node.obj {
3242
if obj.sym == self.variable_name {
3343
if let swc_ecma_ast::MemberProp::Ident(ref prop) = node.prop {
34-
self.properties.insert(prop.sym.to_string());
44+
let loc = self.source_map.lookup_char_pos(node.span.lo());
45+
self.properties.insert(UsedClassName {
46+
class_name: prop.sym.to_string(),
47+
file_name: self.file_name.clone(),
48+
line: loc.line,
49+
column: loc.col.0,
50+
});
3551
}
3652
}
3753
}
@@ -40,12 +56,18 @@ impl Visit for PropertyFinder {
4056
}
4157
}
4258

43-
pub fn extract_used_classes(tsx_code: &str, variable_name: &str) -> HashSet<String> {
44-
let module = module_parser(tsx_code);
59+
pub fn extract_used_classes(
60+
tsx_code: &str,
61+
variable_name: &str,
62+
file_name: String,
63+
) -> HashSet<UsedClassName> {
64+
let (module, source_map) = module_parser(tsx_code);
4565

4666
let mut finder = PropertyFinder {
4767
variable_name: variable_name.to_string(),
68+
file_name,
4869
properties: HashSet::new(),
70+
source_map: source_map.clone(),
4971
};
5072

5173
module.visit_with(&mut finder);
@@ -77,7 +99,7 @@ impl Visit for DefaultCssImportFinder {
7799
}
78100

79101
pub fn extract_default_css_imports(tsx_code: &str) -> HashSet<(String, String)> {
80-
let module = module_parser(tsx_code);
102+
let (module, _) = module_parser(tsx_code);
81103

82104
let mut finder = DefaultCssImportFinder {
83105
imported_variables: HashSet::new(),

0 commit comments

Comments
 (0)