Skip to content

Commit d2c362b

Browse files
new features
1 parent 8d5dadd commit d2c362b

24 files changed

+867
-432
lines changed

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "css-linter"
3-
version = "1.8.2"
3+
version = "1.9.0"
44
edition = "2021"
55

66
[dependencies]
@@ -11,3 +11,4 @@ swc_ecma_ast = "8.0.0"
1111
swc_ecma_parser = { version = "10.0.0", features = ["typescript"] }
1212
swc_ecma_visit = "8.0.0"
1313
anyhow = "1.0.97"
14+
regex = "1.11.1"

src/main.rs

Lines changed: 16 additions & 225 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,34 @@
1-
use std::{
2-
collections::{HashMap, HashSet},
3-
env, fs,
4-
path::{Path, PathBuf},
5-
process,
6-
};
1+
use std::{env, process};
72

83
use anyhow::Result;
9-
use config::get_compiler_options;
10-
use css_parser::{extract_classes, ClassName};
11-
use tsx_parser::{extract_default_css_imports, extract_used_classes, UsedClassName};
12-
use utils::{process_relative_import, replace_aliases};
4+
use modules::{
5+
css_class::get_class_body, defined_classes::get_defined_classes, linter,
6+
styles_imports::get_styles_imports,
7+
};
138

149
mod config;
15-
mod css_parser;
16-
mod tsx_parser;
10+
mod modules;
11+
mod parsers;
1712
mod utils;
1813

19-
fn list_files_in_directory(path: PathBuf, exclude: Vec<String>) -> Vec<String> {
20-
let mut files = Vec::new();
21-
22-
if let Ok(entries) = fs::read_dir(&path) {
23-
for entry in entries.flatten() {
24-
let path = entry.path();
25-
26-
if path.is_dir() {
27-
if let Some(p) = path.file_name() {
28-
let p_str = p.to_string_lossy();
29-
if p_str.starts_with('.') || exclude.iter().any(|i| p_str == *i) {
30-
continue;
31-
}
32-
}
33-
files.extend(list_files_in_directory(path, exclude.clone()));
34-
} else if path.is_file() {
35-
if let Some(path_str) = path.to_str() {
36-
files.push(path_str.to_string());
37-
}
38-
}
39-
}
40-
} else {
41-
eprintln!("Cannot open target dir: {:?}", path);
42-
}
43-
44-
files
45-
}
46-
4714
fn main() -> Result<()> {
48-
const COLOR_BLUE: &str = "\x1b[34m";
49-
const COLOR_YELLOW: &str = "\x1b[33m";
50-
const COLOR_GREEN: &str = "\x1b[32m";
5115
const COLOR_RED: &str = "\x1b[31m";
5216
const COLOR_RESET: &str = "\u{001B}[0m";
5317

5418
let args: Vec<String> = env::args().collect();
19+
if let Ok(cwd) = std::env::var("cwd") {
20+
env::set_current_dir(cwd)?;
21+
}
5522

56-
let path = match args.get(1) {
23+
match args.get(1) {
5724
Some(arg) if arg == "-v" => {
58-
println!("v{}", env!("CARGO_PKG_VERSION"));
25+
print!("v{}", env!("CARGO_PKG_VERSION"));
5926
process::exit(0);
6027
}
61-
Some(arg) if arg == "--lint" => args.get(2).unwrap_or_else(|| {
62-
eprintln!(
63-
"\n{}Error{}: Linting path must be specified",
64-
COLOR_RED, COLOR_RESET
65-
);
66-
process::exit(1);
67-
}),
28+
Some(arg) if arg == "--lint" => linter::lint()?,
29+
Some(arg) if arg == "--imports" => get_styles_imports()?,
30+
Some(arg) if arg == "--classes" => get_defined_classes()?,
31+
Some(arg) if arg == "--class" => get_class_body()?,
6832
Some(arg) => {
6933
eprintln!(
7034
"{}Error{}: Invalid argument: {}",
@@ -75,178 +39,5 @@ fn main() -> Result<()> {
7539
None => todo!(),
7640
};
7741

78-
let minify = args.get(3).map_or("", |v| v) == "--minify";
79-
80-
if let Err(e) = env::set_current_dir(Path::new(path)) {
81-
eprintln!(
82-
"\n{}Error{}: Failed to set current directory: {}",
83-
COLOR_RED, COLOR_RESET, e
84-
);
85-
process::exit(1);
86-
}
87-
let tsconfig = get_compiler_options().unwrap_or_else(|e| {
88-
eprintln!(
89-
"\n{}Error{}: Could not load tsconfig.json. Is the provided directory a typescript project? ({})",
90-
COLOR_RED, COLOR_RESET, e
91-
);
92-
process::exit(1);
93-
});
94-
95-
let dir = list_files_in_directory(Path::new(".").to_path_buf(), tsconfig.exclude);
96-
97-
let mut used_classnames: HashMap<String, HashSet<UsedClassName>> = Default::default();
98-
let mut defined_classnames: HashMap<String, HashSet<ClassName>> = Default::default();
99-
100-
for entry in &dir {
101-
let path = entry.replace("\\", "/");
102-
103-
if path.ends_with(".tsx") {
104-
let code = fs::read_to_string(entry)?;
105-
let imported_css = extract_default_css_imports(&code).unwrap_or_else(|e| {
106-
eprintln!("Could not parse file: {}\n{}", entry, e);
107-
process::exit(1);
108-
});
109-
110-
for (mut style_path, class_names) in imported_css {
111-
process_relative_import(Path::new(entry), &mut style_path)?;
112-
replace_aliases(&mut style_path, tsconfig.compiler_options.paths.clone());
113-
114-
let used_fields = extract_used_classes(&code, &class_names, path.clone())
115-
.unwrap_or_else(|e| {
116-
eprintln!("Could not parse file: {}\n{}", entry, e);
117-
process::exit(1);
118-
});
119-
120-
used_classnames
121-
.entry(style_path)
122-
.or_insert_with(HashSet::new)
123-
.extend(used_fields);
124-
}
125-
} else if path.ends_with(".module.css") {
126-
let code = fs::read_to_string(entry)?;
127-
let classes = extract_classes(&code);
128-
defined_classnames
129-
.entry(path)
130-
.or_insert_with(HashSet::new)
131-
.extend(classes);
132-
}
133-
}
134-
135-
let mut files_count = 0;
136-
let mut errors_count = 0;
137-
138-
for (css_file, mut classes_tsx) in defined_classnames.clone() {
139-
if let Some(used_css) = used_classnames.get(&css_file) {
140-
let used_css_flatten: Vec<String> =
141-
used_css.iter().map(|v| v.class_name.clone()).collect();
142-
classes_tsx.retain(|v| !used_css_flatten.contains(&v.class_name));
143-
}
144-
145-
if classes_tsx.is_empty() {
146-
continue;
147-
}
148-
149-
files_count += 1;
150-
errors_count += classes_tsx.len();
151-
152-
if !minify {
153-
println!("{}{}{}", COLOR_BLUE, css_file, COLOR_RESET);
154-
}
155-
156-
for extra in classes_tsx {
157-
if !minify {
158-
println!(
159-
"{}{}:{} {}Warn{}: Unused class `{}` found.",
160-
COLOR_YELLOW,
161-
extra.line_index + 1,
162-
extra.column_index + 1,
163-
COLOR_YELLOW,
164-
COLOR_RESET,
165-
extra.class_name
166-
);
167-
} else {
168-
println!(
169-
"{}:{}:{}:{}:\"{}\": Unused class found.",
170-
css_file,
171-
extra.line_index + 1,
172-
extra.column_index + 1,
173-
extra.class_name.len(),
174-
extra.class_name
175-
);
176-
}
177-
}
178-
179-
if !minify {
180-
println!();
181-
}
182-
}
183-
184-
let mut undefined_classes: HashMap<String, HashSet<UsedClassName>> = HashMap::new();
185-
186-
for (tsx_file, mut classes) in used_classnames {
187-
if let Some(defined_css) = defined_classnames.get(&tsx_file) {
188-
let defined_css: HashSet<String> =
189-
defined_css.iter().map(|v| v.class_name.clone()).collect();
190-
classes.retain(|v| !defined_css.contains(v.class_name.as_str()));
191-
}
192-
193-
if classes.is_empty() {
194-
continue;
195-
}
196-
197-
files_count += 1;
198-
errors_count += classes.len();
199-
200-
for extra in classes {
201-
undefined_classes
202-
.entry(extra.file_name.clone())
203-
.or_insert_with(HashSet::new)
204-
.insert(extra);
205-
}
206-
}
207-
208-
for undefined in undefined_classes {
209-
if !minify {
210-
println!("{}{}{}", COLOR_BLUE, undefined.0, COLOR_RESET);
211-
}
212-
for extra in undefined.1 {
213-
if !minify {
214-
println!(
215-
"{}{}:{} {}Warn{}: Undefined class `{}` was used.",
216-
COLOR_YELLOW,
217-
extra.line,
218-
extra.column + 1,
219-
COLOR_YELLOW,
220-
COLOR_RESET,
221-
extra.class_name
222-
);
223-
} else {
224-
println!(
225-
"{}:{}:{}:{}:\"{}\": Undefined class was used.",
226-
undefined.0,
227-
extra.line,
228-
extra.column + 1,
229-
extra.class_name.len(),
230-
extra.class_name
231-
);
232-
}
233-
}
234-
235-
if !minify {
236-
println!();
237-
}
238-
}
239-
240-
if !minify {
241-
if errors_count == 0 {
242-
println!("{}✔{} No CSS lint warnings found", COLOR_GREEN, COLOR_RESET);
243-
} else {
244-
println!(
245-
"Found {}{} warnings{} in {} files",
246-
COLOR_YELLOW, errors_count, COLOR_RESET, files_count
247-
);
248-
}
249-
}
250-
25142
Ok(())
25243
}

src/modules/css_class.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
use std::{env, fs, process};
2+
3+
use anyhow::Result;
4+
use regex::Regex;
5+
6+
pub fn get_class_body() -> Result<()> {
7+
const COLOR_RED: &str = "\x1b[31m";
8+
const COLOR_RESET: &str = "\u{001B}[0m";
9+
10+
let args: Vec<String> = env::args().collect();
11+
let path = match args.get(2) {
12+
Some(path) if !path.ends_with(".module.css") => {
13+
eprintln!("{}Error{}: Invalid file extension.", COLOR_RED, COLOR_RESET);
14+
process::exit(1);
15+
}
16+
Some(path) => path,
17+
None => {
18+
eprintln!("Path to the file must be provided");
19+
process::exit(1);
20+
}
21+
};
22+
23+
let class_name = args.get(3).unwrap_or_else(|| {
24+
eprintln!("Class name must be provided");
25+
process::exit(1);
26+
});
27+
28+
let code = fs::read_to_string(path)?;
29+
let pattern = format!(
30+
r"\.{}[^A-Za-z0-9_-][^{{}}]*\{{[^}}]*\}}",
31+
regex::escape(class_name)
32+
);
33+
let re = Regex::new(&pattern)?;
34+
35+
for m in re.find_iter(&code) {
36+
if m.as_str().starts_with(&format!(".{}", class_name)) {
37+
let trimmed = m.as_str().trim_start();
38+
39+
match trimmed {
40+
s if s.starts_with('.') || s.ends_with('}') => print!("{}", trimmed),
41+
_ => print!(" {}", trimmed),
42+
}
43+
break;
44+
}
45+
}
46+
Ok(())
47+
}

src/modules/defined_classes.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
use std::{env, fs, process};
2+
3+
use anyhow::Result;
4+
5+
use crate::parsers::extract_classes;
6+
7+
pub fn get_defined_classes() -> Result<()> {
8+
const COLOR_RED: &str = "\x1b[31m";
9+
const COLOR_RESET: &str = "\u{001B}[0m";
10+
11+
let args: Vec<String> = env::args().collect();
12+
let path = match args.get(2) {
13+
Some(path) if !path.ends_with(".module.css") => {
14+
eprintln!("{}Error{}: Invalid file extension.", COLOR_RED, COLOR_RESET);
15+
process::exit(1);
16+
}
17+
Some(path) => path,
18+
None => {
19+
eprintln!("Path to the file must be provided");
20+
process::exit(1);
21+
}
22+
};
23+
24+
let code = fs::read_to_string(path)?;
25+
let imported_css = extract_classes(&code);
26+
27+
imported_css
28+
.iter()
29+
.try_for_each(|class_name| -> Result<()> {
30+
println!(
31+
"{}:{}:{}",
32+
class_name.class_name, class_name.line_index, class_name.column_index
33+
);
34+
Ok(())
35+
})?;
36+
Ok(())
37+
}

0 commit comments

Comments
 (0)