Skip to content

Commit d8e49a1

Browse files
committedMar 29, 2025·
refactor(linter): compute lintable extensions at compile time (#10090)
Previously, we were wasting some time creating a `Vec` at runtime with the lintable extensions, which is all of the JS/TS extensions plus other frameworks extensions like `.vue`. Now, we precompute this as a static string slice at compile time. This also ensures that we are using the same extensions list between `oxlint` and the language server too. Theoretically this is a very minor perf win possibly?
1 parent 1ab8871 commit d8e49a1

File tree

22 files changed

+199
-37
lines changed

22 files changed

+199
-37
lines changed
 

‎.vscode/extensions.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@
55
"tamasfe.even-better-toml", // toml syntax
66
"nefrob.vscode-just-syntax", // justfile syntax & task runner
77
"tekumara.typos-vscode", // spell checker
8-
"EditorConfig.EditorConfig", // editorconfig
8+
"EditorConfig.EditorConfig" // editorconfig
99
]
1010
}

‎Cargo.lock

+7-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ bpaf = "0.9.16"
168168
bumpalo = "=3.17.0"
169169
compact_str = "0.9.0"
170170
console = "0.15.10"
171+
constcat = "0.6.0"
171172
convert_case = "0.8.0"
172173
cow-utils = "0.1.3"
173174
criterion2 = { version = "3.0.0", default-features = false }

‎apps/oxlint/src/lint.rs

+3-10
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,15 @@ use ignore::{gitignore::Gitignore, overrides::OverrideBuilder};
1010
use oxc_diagnostics::{DiagnosticService, GraphicalReportHandler, OxcDiagnostic};
1111
use oxc_linter::{
1212
AllowWarnDeny, ConfigStore, ConfigStoreBuilder, InvalidFilterKind, LintFilter, LintOptions,
13-
LintService, LintServiceOptions, Linter, Oxlintrc, loader::LINT_PARTIAL_LOADER_EXT,
13+
LintService, LintServiceOptions, Linter, Oxlintrc,
1414
};
15-
use oxc_span::VALID_EXTENSIONS;
1615
use rustc_hash::{FxHashMap, FxHashSet};
1716
use serde_json::Value;
1817

1918
use crate::{
2019
cli::{CliRunResult, LintCommand, MiscOptions, ReportUnusedDirectives, Runner, WarningOptions},
2120
output_formatter::{LintCommandInfo, OutputFormatter},
22-
walk::{Extensions, Walk},
21+
walk::Walk,
2322
};
2423

2524
#[derive(Debug)]
@@ -80,12 +79,6 @@ impl Runner for LintRunner {
8079
}
8180
};
8281

83-
let extensions = VALID_EXTENSIONS
84-
.iter()
85-
.chain(LINT_PARTIAL_LOADER_EXT.iter())
86-
.copied()
87-
.collect::<Vec<&'static str>>();
88-
8982
let config_search_result =
9083
Self::find_oxlint_config(&self.cwd, basic_options.config.as_ref());
9184

@@ -172,7 +165,7 @@ impl Runner for LintRunner {
172165
}
173166

174167
let walker = Walk::new(&paths, &ignore_options, override_builder);
175-
let paths = walker.with_extensions(Extensions(extensions)).paths();
168+
let paths = walker.paths();
176169

177170
let number_of_files = paths.len();
178171

‎apps/oxlint/src/walk.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::{ffi::OsStr, path::PathBuf, sync::Arc, sync::mpsc};
22

33
use ignore::{DirEntry, overrides::Override};
4-
use oxc_span::VALID_EXTENSIONS;
4+
use oxc_linter::LINTABLE_EXTENSIONS;
55

66
use crate::cli::IgnoreOptions;
77

@@ -10,7 +10,7 @@ pub struct Extensions(pub Vec<&'static str>);
1010

1111
impl Default for Extensions {
1212
fn default() -> Self {
13-
Self(VALID_EXTENSIONS.to_vec())
13+
Self(LINTABLE_EXTENSIONS.to_vec())
1414
}
1515
}
1616

@@ -108,6 +108,7 @@ impl Walk {
108108
receiver.into_iter().flatten().collect()
109109
}
110110

111+
#[cfg_attr(not(test), expect(dead_code))]
111112
pub fn with_extensions(mut self, extensions: Extensions) -> Self {
112113
self.extensions = extensions;
113114
self

‎crates/oxc_language_server/Cargo.toml

-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ oxc_diagnostics = { workspace = true }
2828
oxc_linter = { workspace = true }
2929
oxc_parser = { workspace = true }
3030
oxc_semantic = { workspace = true }
31-
oxc_span = { workspace = true }
3231

3332
cow-utils = { workspace = true }
3433
env_logger = { workspace = true, features = ["humantime"] }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
debugger
3+
---
4+
5+
<!-- Store the message prop as a data attribute. -->
6+
<astro-greet data-message={message}>
7+
<button>Say hi!</button>
8+
</astro-greet>
9+
10+
<script asdf >
11+
debugger
12+
</script>
13+
14+
<script asdf>
15+
debugger
16+
</script>
17+
18+
<script>
19+
debugger
20+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<script>
2+
debugger;
3+
4+
export let title;
5+
export let person;
6+
7+
// this will update `document.title` whenever
8+
// the `title` prop changes
9+
$: document.title = title;
10+
11+
$: {
12+
console.log(`multiple statements can be combined`);
13+
console.log(`the current title is ${title}`);
14+
}
15+
16+
// this will update `name` when 'person' changes
17+
$: ({ name } = person);
18+
19+
// don't do this. it will run before the previous line
20+
let name2 = name;
21+
</script>
22+
23+
<h1>Hello {name}!</h1>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<template>
2+
<div>Hello World</div>
3+
</template>
4+
5+
<script>
6+
debugger
7+
</script>
8+
9+
<script setup lang="ts" generic="T extends Record<string, string>">
10+
let foo: T; // test ts syntax
11+
debugger;
12+
</script>

‎crates/oxc_language_server/src/linter/isolated_lint_handler.rs

+4-6
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,11 @@ use tower_lsp::lsp_types::{self, DiagnosticRelatedInformation, DiagnosticSeverit
1212
use oxc_allocator::Allocator;
1313
use oxc_diagnostics::{Error, NamedSource};
1414
use oxc_linter::{
15-
Linter, ModuleRecord,
16-
loader::{JavaScriptSource, LINT_PARTIAL_LOADER_EXT, Loader},
15+
LINTABLE_EXTENSIONS, Linter, ModuleRecord,
16+
loader::{JavaScriptSource, Loader},
1717
};
1818
use oxc_parser::{ParseOptions, Parser};
1919
use oxc_semantic::SemanticBuilder;
20-
use oxc_span::VALID_EXTENSIONS;
2120

2221
use crate::DiagnosticReport;
2322
use crate::linter::error_with_position::{ErrorReport, ErrorWithPosition, FixedContent};
@@ -187,9 +186,8 @@ impl IsolatedLintHandler {
187186

188187
fn should_lint_path(path: &Path) -> bool {
189188
static WANTED_EXTENSIONS: OnceLock<FxHashSet<&'static str>> = OnceLock::new();
190-
let wanted_exts = WANTED_EXTENSIONS.get_or_init(|| {
191-
VALID_EXTENSIONS.iter().chain(LINT_PARTIAL_LOADER_EXT.iter()).copied().collect()
192-
});
189+
let wanted_exts =
190+
WANTED_EXTENSIONS.get_or_init(|| LINTABLE_EXTENSIONS.iter().copied().collect());
193191

194192
path.extension()
195193
.and_then(std::ffi::OsStr::to_str)

‎crates/oxc_language_server/src/linter/server_linter.rs

+7
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,11 @@ mod test {
9090
Tester::new_with_linter(linter)
9191
.test_and_snapshot_single_file("fixtures/linter/regexp_feature/index.ts");
9292
}
93+
94+
#[test]
95+
fn test_frameworks() {
96+
Tester::new().test_and_snapshot_single_file("fixtures/linter/astro/debugger.astro");
97+
Tester::new().test_and_snapshot_single_file("fixtures/linter/vue/debugger.vue");
98+
Tester::new().test_and_snapshot_single_file("fixtures/linter/svelte/debugger.svelte");
99+
}
93100
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
---
2+
source: crates/oxc_language_server/src/linter/tester.rs
3+
---
4+
code: "eslint(no-debugger)"
5+
code_description.href: "https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-debugger"
6+
message: "`debugger` statement is not allowed\nhelp: Delete this code."
7+
range: Range { start: Position { line: 1, character: 0 }, end: Position { line: 1, character: 8 } }
8+
related_information[0].message: ""
9+
related_information[0].location.uri: "file://<variable>/fixtures/linter/astro/debugger.astro"
10+
related_information[0].location.range: Range { start: Position { line: 1, character: 0 }, end: Position { line: 1, character: 8 } }
11+
severity: Some(Warning)
12+
source: Some("oxc")
13+
tags: None
14+
15+
16+
code: "eslint(no-debugger)"
17+
code_description.href: "https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-debugger"
18+
message: "`debugger` statement is not allowed\nhelp: Delete this code."
19+
range: Range { start: Position { line: 10, character: 2 }, end: Position { line: 10, character: 10 } }
20+
related_information[0].message: ""
21+
related_information[0].location.uri: "file://<variable>/fixtures/linter/astro/debugger.astro"
22+
related_information[0].location.range: Range { start: Position { line: 10, character: 2 }, end: Position { line: 10, character: 10 } }
23+
severity: Some(Warning)
24+
source: Some("oxc")
25+
tags: None
26+
27+
28+
code: "eslint(no-debugger)"
29+
code_description.href: "https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-debugger"
30+
message: "`debugger` statement is not allowed\nhelp: Delete this code."
31+
range: Range { start: Position { line: 14, character: 2 }, end: Position { line: 14, character: 10 } }
32+
related_information[0].message: ""
33+
related_information[0].location.uri: "file://<variable>/fixtures/linter/astro/debugger.astro"
34+
related_information[0].location.range: Range { start: Position { line: 14, character: 2 }, end: Position { line: 14, character: 10 } }
35+
severity: Some(Warning)
36+
source: Some("oxc")
37+
tags: None
38+
39+
40+
code: "eslint(no-debugger)"
41+
code_description.href: "https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-debugger"
42+
message: "`debugger` statement is not allowed\nhelp: Delete this code."
43+
range: Range { start: Position { line: 18, character: 2 }, end: Position { line: 18, character: 10 } }
44+
related_information[0].message: ""
45+
related_information[0].location.uri: "file://<variable>/fixtures/linter/astro/debugger.astro"
46+
related_information[0].location.range: Range { start: Position { line: 18, character: 2 }, end: Position { line: 18, character: 10 } }
47+
severity: Some(Warning)
48+
source: Some("oxc")
49+
tags: None
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
source: crates/oxc_language_server/src/linter/tester.rs
3+
---
4+
code: "eslint(no-debugger)"
5+
code_description.href: "https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-debugger"
6+
message: "`debugger` statement is not allowed\nhelp: Delete this code."
7+
range: Range { start: Position { line: 1, character: 1 }, end: Position { line: 1, character: 10 } }
8+
related_information[0].message: ""
9+
related_information[0].location.uri: "file://<variable>/fixtures/linter/svelte/debugger.svelte"
10+
related_information[0].location.range: Range { start: Position { line: 1, character: 1 }, end: Position { line: 1, character: 10 } }
11+
severity: Some(Warning)
12+
source: Some("oxc")
13+
tags: None
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
source: crates/oxc_language_server/src/linter/tester.rs
3+
---
4+
code: "eslint(no-debugger)"
5+
code_description.href: "https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-debugger"
6+
message: "`debugger` statement is not allowed\nhelp: Delete this code."
7+
range: Range { start: Position { line: 5, character: 4 }, end: Position { line: 5, character: 12 } }
8+
related_information[0].message: ""
9+
related_information[0].location.uri: "file://<variable>/fixtures/linter/vue/debugger.vue"
10+
related_information[0].location.range: Range { start: Position { line: 5, character: 4 }, end: Position { line: 5, character: 12 } }
11+
severity: Some(Warning)
12+
source: Some("oxc")
13+
tags: None
14+
15+
16+
code: "eslint(no-debugger)"
17+
code_description.href: "https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-debugger"
18+
message: "`debugger` statement is not allowed\nhelp: Delete this code."
19+
range: Range { start: Position { line: 10, character: 4 }, end: Position { line: 10, character: 13 } }
20+
related_information[0].message: ""
21+
related_information[0].location.uri: "file://<variable>/fixtures/linter/vue/debugger.vue"
22+
related_information[0].location.range: Range { start: Position { line: 10, character: 4 }, end: Position { line: 10, character: 13 } }
23+
severity: Some(Warning)
24+
source: Some("oxc")
25+
tags: None

‎crates/oxc_language_server/src/linter/tester.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,10 @@ impl Tester<'_> {
102102
#[expect(clippy::disallowed_methods)]
103103
pub fn test_and_snapshot_single_file(&self, relative_file_path: &str) {
104104
let uri = get_file_uri(relative_file_path);
105-
let content = std::fs::read_to_string(uri.to_file_path().unwrap())
106-
.expect("could not read fixture file");
105+
let content = match std::fs::read_to_string(uri.to_file_path().unwrap()) {
106+
Ok(content) => content,
107+
Err(err) => panic!("could not read fixture file: {err}: {relative_file_path}"),
108+
};
107109
let reports = self.server_linter.run_single(&uri, Some(content)).unwrap();
108110
let snapshot = if reports.is_empty() {
109111
"No diagnostic reports".to_string()

‎crates/oxc_linter/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ oxc_syntax = { workspace = true, features = ["serialize"] }
4141

4242
#
4343
bitflags = { workspace = true }
44+
constcat = { workspace = true }
4445
convert_case = { workspace = true }
4546
cow-utils = { workspace = true }
4647
fast-glob = { workspace = true }

‎crates/oxc_linter/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ pub use crate::{
3838
context::LintContext,
3939
fixer::FixKind,
4040
frameworks::FrameworkFlags,
41+
loader::LINTABLE_EXTENSIONS,
4142
module_record::ModuleRecord,
4243
options::LintOptions,
4344
options::{AllowWarnDeny, InvalidFilterKind, LintFilter, LintFilterKind},

‎crates/oxc_linter/src/loader/mod.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use oxc_span::SourceType;
44

55
mod partial_loader;
66
mod source;
7-
pub use partial_loader::{LINT_PARTIAL_LOADER_EXT, PartialLoader};
7+
pub use partial_loader::{LINT_PARTIAL_LOADER_EXTENSIONS, LINTABLE_EXTENSIONS, PartialLoader};
88
pub use source::JavaScriptSource;
99

1010
// TODO: use oxc_resolver::FileSystem. We can't do so until that crate exposes FileSystemOs
@@ -19,7 +19,7 @@ impl Loader {
1919
|| path
2020
.extension()
2121
.and_then(std::ffi::OsStr::to_str)
22-
.is_some_and(|ext| LINT_PARTIAL_LOADER_EXT.contains(&ext))
22+
.is_some_and(|ext| LINT_PARTIAL_LOADER_EXTENSIONS.contains(&ext))
2323
}
2424

2525
/// # Errors

‎crates/oxc_linter/src/loader/partial_loader/mod.rs

+10-1
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,22 @@ mod astro;
22
mod svelte;
33
mod vue;
44

5+
use oxc_span::VALID_EXTENSIONS;
6+
57
pub use self::{astro::AstroPartialLoader, svelte::SveltePartialLoader, vue::VuePartialLoader};
68
use crate::loader::JavaScriptSource;
79

810
const SCRIPT_START: &str = "<script";
911
const SCRIPT_END: &str = "</script>";
1012

11-
pub const LINT_PARTIAL_LOADER_EXT: &[&str] = &["vue", "astro", "svelte"];
13+
/// File extensions that can contain JS/TS code in certain parts, such as in `<script>` tags, and can
14+
/// be loaded using the [`PartialLoader`].
15+
pub const LINT_PARTIAL_LOADER_EXTENSIONS: &[&str] = &["vue", "astro", "svelte"];
16+
17+
/// All valid JavaScript/TypeScript extensions, plus additional framework files that
18+
/// contain JavaScript/TypeScript code in them (e.g., Vue, Astro, Svelte, etc.).
19+
pub const LINTABLE_EXTENSIONS: &[&str] =
20+
constcat::concat_slices!([&str]: VALID_EXTENSIONS, LINT_PARTIAL_LOADER_EXTENSIONS);
1221

1322
pub struct PartialLoader;
1423

‎crates/oxc_linter/src/rules/unicorn/no_empty_file.rs

+10-8
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ use oxc_macros::declare_oxc_lint;
44
use oxc_span::Span;
55

66
use crate::{
7-
context::LintContext, loader::LINT_PARTIAL_LOADER_EXT, rule::Rule, utils::is_empty_stmt,
7+
context::{ContextHost, LintContext},
8+
loader::LINT_PARTIAL_LOADER_EXTENSIONS,
9+
rule::Rule,
10+
utils::is_empty_stmt,
811
};
912

1013
fn no_empty_file_diagnostic(span: Span) -> OxcDiagnostic {
@@ -39,13 +42,6 @@ declare_oxc_lint!(
3942

4043
impl Rule for NoEmptyFile {
4144
fn run_once(&self, ctx: &LintContext) {
42-
if ctx
43-
.file_path()
44-
.extension()
45-
.is_some_and(|ext| LINT_PARTIAL_LOADER_EXT.contains(&ext.to_string_lossy().as_ref()))
46-
{
47-
return;
48-
}
4945
let Some(root) = ctx.nodes().root_node() else {
5046
return;
5147
};
@@ -67,6 +63,12 @@ impl Rule for NoEmptyFile {
6763
span.end = std::cmp::min(span.end, 100);
6864
ctx.diagnostic(no_empty_file_diagnostic(span));
6965
}
66+
67+
fn should_run(&self, ctx: &ContextHost) -> bool {
68+
ctx.file_path().extension().is_some_and(|ext| {
69+
!LINT_PARTIAL_LOADER_EXTENSIONS.contains(&ext.to_string_lossy().as_ref())
70+
})
71+
}
7072
}
7173

7274
fn has_triple_slash_directive(ctx: &LintContext<'_>) -> bool {

‎crates/oxc_linter/src/service/runtime.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use oxc_span::{CompactStr, SourceType, VALID_EXTENSIONS};
2525
use super::LintServiceOptions;
2626
use crate::{
2727
Fixer, Linter, Message,
28-
loader::{JavaScriptSource, LINT_PARTIAL_LOADER_EXT, PartialLoader},
28+
loader::{JavaScriptSource, LINT_PARTIAL_LOADER_EXTENSIONS, PartialLoader},
2929
module_record::ModuleRecord,
3030
utils::read_to_string,
3131
};
@@ -175,7 +175,7 @@ impl Runtime {
175175
) -> Option<Result<(SourceType, String), Error>> {
176176
let source_type = SourceType::from_path(path);
177177
let not_supported_yet =
178-
source_type.as_ref().is_err_and(|_| !LINT_PARTIAL_LOADER_EXT.contains(&ext));
178+
source_type.as_ref().is_err_and(|_| !LINT_PARTIAL_LOADER_EXTENSIONS.contains(&ext));
179179
if not_supported_yet {
180180
return None;
181181
}

‎crates/oxc_span/src/source_type/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ impl ContentEq for SourceType {
9797
}
9898

9999
/// Valid file extensions
100-
pub const VALID_EXTENSIONS: [&str; 8] = ["js", "mjs", "cjs", "jsx", "ts", "mts", "cts", "tsx"];
100+
pub const VALID_EXTENSIONS: &[&str] = &["js", "mjs", "cjs", "jsx", "ts", "mts", "cts", "tsx"];
101101

102102
impl SourceType {
103103
/// Creates a [`SourceType`] representing a regular [`JavaScript`] file.

0 commit comments

Comments
 (0)
Please sign in to comment.