Skip to content

Commit 78a1146

Browse files
committed
feat(oxfmt): Support more extensions from linguist-languages (#13607)
Part of #10573 Prettier supports many more file extensions than those listed in `oxc_span::VALID_EXTENSIONS`. Refs: - https://github.com/ikatyang-collab/linguist-languages/blob/main/data/JavaScript.js - https://github.com/ikatyang-collab/linguist-languages/blob/main/data/TypeScript.js At the same time, I simplified the file extension check to a simple function.
1 parent 59b2420 commit 78a1146

File tree

5 files changed

+90
-69
lines changed

5 files changed

+90
-69
lines changed

Cargo.lock

Lines changed: 0 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/oxfmt/Cargo.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,8 @@ oxc_span = { workspace = true }
3535

3636
bpaf = { workspace = true, features = ["autocomplete", "bright-color", "derive"] }
3737
ignore = { workspace = true, features = ["simd-accel"] }
38-
indexmap = { workspace = true }
3938
miette = { workspace = true }
4039
rayon = { workspace = true }
41-
rustc-hash = { workspace = true }
4240
tracing-subscriber = { workspace = true, features = [] } # Omit the `regex` feature
4341

4442
[target.'cfg(not(any(target_os = "linux", target_os = "freebsd", target_arch = "arm", target_family = "wasm")))'.dependencies]

apps/oxfmt/src/format.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::{env, ffi::OsStr, io::Write, path::PathBuf, sync::Arc};
1+
use std::{env, io::Write, path::PathBuf};
22

33
use ignore::overrides::OverrideBuilder;
44

@@ -50,12 +50,12 @@ impl FormatRunner {
5050
.flatten();
5151

5252
let walker = Walk::new(&regular_paths, override_builder);
53-
let paths = walker.paths();
53+
let entries = walker.entries();
5454

55-
let files_to_format = paths
55+
let files_to_format = entries
5656
.into_iter()
57-
// .filter(|path| !config_store.should_ignore(Path::new(path)))
58-
.collect::<Vec<Arc<OsStr>>>();
57+
// .filter(|entry| !config_store.should_ignore(Path::new(&entry.path)))
58+
.collect::<Vec<_>>();
5959

6060
if files_to_format.is_empty() {
6161
print_and_flush_stdout(stdout, "Expected at least one target file\n");
@@ -67,7 +67,7 @@ impl FormatRunner {
6767

6868
rayon::spawn(move || {
6969
let mut format_service = FormatService::new(cwd, &output_options);
70-
format_service.with_paths(files_to_format);
70+
format_service.with_entries(files_to_format);
7171
format_service.run(&tx_error);
7272
});
7373
// NOTE: This is a blocking

apps/oxfmt/src/service.rs

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,39 @@
1-
use std::{ffi::OsStr, fs, path::Path, sync::Arc, time::Instant};
1+
use std::{fs, path::Path, time::Instant};
22

3-
use indexmap::IndexSet;
43
use rayon::prelude::*;
5-
use rustc_hash::FxBuildHasher;
64

75
use oxc_allocator::Allocator;
86
use oxc_diagnostics::{DiagnosticSender, DiagnosticService, OxcDiagnostic, Severity};
97
use oxc_formatter::{FormatOptions, Formatter};
108
use oxc_parser::{ParseOptions, Parser};
11-
use oxc_span::SourceType;
129

13-
use crate::command::OutputOptions;
10+
use crate::{command::OutputOptions, walk::WalkEntry};
1411

1512
pub struct FormatService {
1613
cwd: Box<Path>,
1714
output_options: OutputOptions,
18-
// TODO: Just use `Vec`?
19-
paths: IndexSet<Arc<OsStr>, FxBuildHasher>,
15+
entries: Vec<WalkEntry>,
2016
}
2117

2218
impl FormatService {
2319
pub fn new<T>(cwd: T, output_options: &OutputOptions) -> Self
2420
where
2521
T: Into<Box<Path>>,
2622
{
27-
Self {
28-
cwd: cwd.into(),
29-
output_options: output_options.clone(),
30-
paths: IndexSet::with_capacity_and_hasher(0, FxBuildHasher),
31-
}
23+
Self { cwd: cwd.into(), output_options: output_options.clone(), entries: Vec::new() }
3224
}
3325

34-
pub fn with_paths(&mut self, paths: Vec<Arc<OsStr>>) -> &mut Self {
35-
self.paths = paths.into_iter().collect();
26+
pub fn with_entries(&mut self, entries: Vec<WalkEntry>) -> &mut Self {
27+
self.entries = entries;
3628
self
3729
}
3830

3931
pub fn run(&self, tx_error: &DiagnosticSender) {
40-
self.paths.iter().par_bridge().for_each(|path| {
32+
self.entries.iter().par_bridge().for_each(|entry| {
4133
let start_time = Instant::now();
4234

43-
let path = Path::new(path);
44-
let source_type =
45-
SourceType::from_path(path).expect("`path` should be valid SourceType");
35+
let path = Path::new(&entry.path);
36+
let source_type = entry.source_type;
4637
// TODO: read_to_arena_str()?
4738
let source_text = fs::read_to_string(path).expect("Failed to read file");
4839

apps/oxfmt/src/walk.rs

Lines changed: 75 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,103 @@
11
use std::{ffi::OsStr, path::PathBuf, sync::Arc, sync::mpsc};
22

3-
use ignore::{DirEntry, overrides::Override};
4-
use oxc_span::VALID_EXTENSIONS;
5-
6-
// use crate::cli::IgnoreOptions;
7-
8-
#[derive(Debug, Clone)]
9-
pub struct Extensions(pub Vec<&'static str>);
10-
11-
impl Default for Extensions {
12-
fn default() -> Self {
13-
Self(VALID_EXTENSIONS.to_vec())
3+
use ignore::overrides::Override;
4+
5+
use oxc_span::SourceType;
6+
7+
// Additional extensions from linguist-languages, which Prettier also supports
8+
// - https://github.com/ikatyang-collab/linguist-languages/blob/d1dc347c7ced0f5b42dd66c7d1c4274f64a3eb6b/data/JavaScript.js
9+
// No special extensions for TypeScript
10+
// - https://github.com/ikatyang-collab/linguist-languages/blob/d1dc347c7ced0f5b42dd66c7d1c4274f64a3eb6b/data/TypeScript.js
11+
const ADDITIONAL_JS_EXTENSIONS: &[&str] = &[
12+
"_js",
13+
"bones",
14+
"es",
15+
"es6",
16+
"frag",
17+
"gs",
18+
"jake",
19+
"javascript",
20+
"jsb",
21+
"jscad",
22+
"jsfl",
23+
"jslib",
24+
"jsm",
25+
"jspre",
26+
"jss",
27+
"njs",
28+
"pac",
29+
"sjs",
30+
"ssjs",
31+
"xsjs",
32+
"xsjslib",
33+
];
34+
35+
fn is_supported_source_type(path: &std::path::Path) -> Option<SourceType> {
36+
let extension = path.extension()?.to_string_lossy();
37+
38+
// Standard extensions, also supported by `oxc_span::VALID_EXTENSIONS`
39+
if let Ok(source_type) = SourceType::from_extension(&extension) {
40+
return Some(source_type);
1441
}
42+
// Additional extensions from linguist-languages, which Prettier also supports
43+
if ADDITIONAL_JS_EXTENSIONS.contains(&extension.as_ref()) {
44+
return Some(SourceType::default());
45+
}
46+
// `Jakefile` has no extension but is a valid JS file defined by linguist-languages
47+
if path.file_name() == Some(OsStr::new("Jakefile")) {
48+
return Some(SourceType::default());
49+
}
50+
51+
None
1552
}
1653

54+
// ---
55+
1756
pub struct Walk {
1857
inner: ignore::WalkParallel,
19-
/// The file extensions to include during the traversal.
20-
extensions: Extensions,
58+
}
59+
60+
pub struct WalkEntry {
61+
pub path: Arc<OsStr>,
62+
pub source_type: SourceType,
2163
}
2264

2365
struct WalkBuilder {
24-
sender: mpsc::Sender<Vec<Arc<OsStr>>>,
25-
extensions: Extensions,
66+
sender: mpsc::Sender<Vec<WalkEntry>>,
2667
}
2768

2869
impl<'s> ignore::ParallelVisitorBuilder<'s> for WalkBuilder {
2970
fn build(&mut self) -> Box<dyn ignore::ParallelVisitor + 's> {
30-
Box::new(WalkCollector {
31-
paths: vec![],
32-
sender: self.sender.clone(),
33-
extensions: self.extensions.clone(),
34-
})
71+
Box::new(WalkCollector { entries: vec![], sender: self.sender.clone() })
3572
}
3673
}
3774

3875
struct WalkCollector {
39-
paths: Vec<Arc<OsStr>>,
40-
sender: mpsc::Sender<Vec<Arc<OsStr>>>,
41-
extensions: Extensions,
76+
entries: Vec<WalkEntry>,
77+
sender: mpsc::Sender<Vec<WalkEntry>>,
4278
}
4379

4480
impl Drop for WalkCollector {
4581
fn drop(&mut self) {
46-
let paths = std::mem::take(&mut self.paths);
47-
self.sender.send(paths).unwrap();
82+
let entries = std::mem::take(&mut self.entries);
83+
self.sender.send(entries).unwrap();
4884
}
4985
}
5086

5187
impl ignore::ParallelVisitor for WalkCollector {
5288
fn visit(&mut self, entry: Result<ignore::DirEntry, ignore::Error>) -> ignore::WalkState {
5389
match entry {
5490
Ok(entry) => {
55-
if Walk::is_wanted_entry(&entry, &self.extensions) {
56-
self.paths.push(entry.path().as_os_str().into());
91+
// Skip if we can't get file type or if it's a directory
92+
if let Some(file_type) = entry.file_type() {
93+
if !file_type.is_dir() {
94+
if let Some(source_type) = is_supported_source_type(entry.path()) {
95+
self.entries.push(WalkEntry {
96+
path: entry.path().as_os_str().into(),
97+
source_type,
98+
});
99+
}
100+
}
57101
}
58102
ignore::WalkState::Continue
59103
}
@@ -87,24 +131,14 @@ impl Walk {
87131
// Do not follow symlinks like Prettier does.
88132
// See https://github.com/prettier/prettier/pull/14627
89133
let inner = inner.hidden(false).ignore(false).git_global(false).build_parallel();
90-
Self { inner, extensions: Extensions::default() }
134+
Self { inner }
91135
}
92136

93-
pub fn paths(self) -> Vec<Arc<OsStr>> {
94-
let (sender, receiver) = mpsc::channel::<Vec<Arc<OsStr>>>();
95-
let mut builder = WalkBuilder { sender, extensions: self.extensions };
137+
pub fn entries(self) -> Vec<WalkEntry> {
138+
let (sender, receiver) = mpsc::channel::<Vec<WalkEntry>>();
139+
let mut builder = WalkBuilder { sender };
96140
self.inner.visit(&mut builder);
97141
drop(builder);
98142
receiver.into_iter().flatten().collect()
99143
}
100-
101-
fn is_wanted_entry(dir_entry: &DirEntry, extensions: &Extensions) -> bool {
102-
let Some(file_type) = dir_entry.file_type() else { return false };
103-
if file_type.is_dir() {
104-
return false;
105-
}
106-
let Some(extension) = dir_entry.path().extension() else { return false };
107-
let extension = extension.to_string_lossy();
108-
extensions.0.contains(&extension.as_ref())
109-
}
110144
}

0 commit comments

Comments
 (0)