mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
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?
This commit is contained in:
1 parent
1ab88718a8
commit
d8e49a15d9
22 files changed
+199
-37
No files matched your search
Vendored
+1
-1
@@ -5,6 +5,6 @@
|
||||
"tamasfe.even-better-toml", // toml syntax
|
||||
"nefrob.vscode-just-syntax", // justfile syntax & task runner
|
||||
"tekumara.typos-vscode", // spell checker
|
||||
"EditorConfig.EditorConfig", // editorconfig
|
||||
"EditorConfig.EditorConfig" // editorconfig
|
||||
]
|
||||
}
|
||||
Generated
+7
-1
@@ -319,6 +319,12 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "constcat"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ffb5df6dd5dadb422897e8132f415d7a054e3cd757e5070b663f75bea1840fb"
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.8.0"
|
||||
@@ -1809,7 +1815,6 @@ dependencies = [
|
||||
"oxc_linter",
|
||||
"oxc_parser",
|
||||
"oxc_semantic",
|
||||
"oxc_span",
|
||||
"papaya",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
@@ -1823,6 +1828,7 @@ name = "oxc_linter"
|
||||
version = "0.16.3"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"constcat",
|
||||
"convert_case",
|
||||
"cow-utils",
|
||||
"fast-glob",
|
||||
|
||||
@@ -168,6 +168,7 @@ bpaf = "0.9.16"
|
||||
bumpalo = "=3.17.0"
|
||||
compact_str = "0.9.0"
|
||||
console = "0.15.10"
|
||||
constcat = "0.6.0"
|
||||
convert_case = "0.8.0"
|
||||
cow-utils = "0.1.3"
|
||||
criterion2 = { version = "3.0.0", default-features = false }
|
||||
|
||||
+3
-10
@@ -10,16 +10,15 @@ use ignore::{gitignore::Gitignore, overrides::OverrideBuilder};
|
||||
use oxc_diagnostics::{DiagnosticService, GraphicalReportHandler, OxcDiagnostic};
|
||||
use oxc_linter::{
|
||||
AllowWarnDeny, ConfigStore, ConfigStoreBuilder, InvalidFilterKind, LintFilter, LintOptions,
|
||||
LintService, LintServiceOptions, Linter, Oxlintrc, loader::LINT_PARTIAL_LOADER_EXT,
|
||||
LintService, LintServiceOptions, Linter, Oxlintrc,
|
||||
};
|
||||
use oxc_span::VALID_EXTENSIONS;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::{
|
||||
cli::{CliRunResult, LintCommand, MiscOptions, ReportUnusedDirectives, Runner, WarningOptions},
|
||||
output_formatter::{LintCommandInfo, OutputFormatter},
|
||||
walk::{Extensions, Walk},
|
||||
walk::Walk,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -80,12 +79,6 @@ impl Runner for LintRunner {
|
||||
}
|
||||
};
|
||||
|
||||
let extensions = VALID_EXTENSIONS
|
||||
.iter()
|
||||
.chain(LINT_PARTIAL_LOADER_EXT.iter())
|
||||
.copied()
|
||||
.collect::<Vec<&'static str>>();
|
||||
|
||||
let config_search_result =
|
||||
Self::find_oxlint_config(&self.cwd, basic_options.config.as_ref());
|
||||
|
||||
@@ -172,7 +165,7 @@ impl Runner for LintRunner {
|
||||
}
|
||||
|
||||
let walker = Walk::new(&paths, &ignore_options, override_builder);
|
||||
let paths = walker.with_extensions(Extensions(extensions)).paths();
|
||||
let paths = walker.paths();
|
||||
|
||||
let number_of_files = paths.len();
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::{ffi::OsStr, path::PathBuf, sync::Arc, sync::mpsc};
|
||||
|
||||
use ignore::{DirEntry, overrides::Override};
|
||||
use oxc_span::VALID_EXTENSIONS;
|
||||
use oxc_linter::LINTABLE_EXTENSIONS;
|
||||
|
||||
use crate::cli::IgnoreOptions;
|
||||
|
||||
@@ -10,7 +10,7 @@ pub struct Extensions(pub Vec<&'static str>);
|
||||
|
||||
impl Default for Extensions {
|
||||
fn default() -> Self {
|
||||
Self(VALID_EXTENSIONS.to_vec())
|
||||
Self(LINTABLE_EXTENSIONS.to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,6 +108,7 @@ impl Walk {
|
||||
receiver.into_iter().flatten().collect()
|
||||
}
|
||||
|
||||
#[cfg_attr(not(test), expect(dead_code))]
|
||||
pub fn with_extensions(mut self, extensions: Extensions) -> Self {
|
||||
self.extensions = extensions;
|
||||
self
|
||||
|
||||
@@ -28,7 +28,6 @@ oxc_diagnostics = { workspace = true }
|
||||
oxc_linter = { workspace = true }
|
||||
oxc_parser = { workspace = true }
|
||||
oxc_semantic = { workspace = true }
|
||||
oxc_span = { workspace = true }
|
||||
|
||||
cow-utils = { workspace = true }
|
||||
env_logger = { workspace = true, features = ["humantime"] }
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
debugger
|
||||
---
|
||||
|
||||
<!-- Store the message prop as a data attribute. -->
|
||||
<astro-greet data-message={message}>
|
||||
<button>Say hi!</button>
|
||||
</astro-greet>
|
||||
|
||||
<script asdf >
|
||||
debugger
|
||||
</script>
|
||||
|
||||
<script asdf>
|
||||
debugger
|
||||
</script>
|
||||
|
||||
<script>
|
||||
debugger
|
||||
</script>
|
||||
@@ -0,0 +1,23 @@
|
||||
<script>
|
||||
debugger;
|
||||
|
||||
export let title;
|
||||
export let person;
|
||||
|
||||
// this will update `document.title` whenever
|
||||
// the `title` prop changes
|
||||
$: document.title = title;
|
||||
|
||||
$: {
|
||||
console.log(`multiple statements can be combined`);
|
||||
console.log(`the current title is ${title}`);
|
||||
}
|
||||
|
||||
// this will update `name` when 'person' changes
|
||||
$: ({ name } = person);
|
||||
|
||||
// don't do this. it will run before the previous line
|
||||
let name2 = name;
|
||||
</script>
|
||||
|
||||
<h1>Hello {name}!</h1>
|
||||
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<div>Hello World</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
debugger
|
||||
</script>
|
||||
|
||||
<script setup lang="ts" generic="T extends Record<string, string>">
|
||||
let foo: T; // test ts syntax
|
||||
debugger;
|
||||
</script>
|
||||
@@ -12,12 +12,11 @@ use tower_lsp::lsp_types::{self, DiagnosticRelatedInformation, DiagnosticSeverit
|
||||
use oxc_allocator::Allocator;
|
||||
use oxc_diagnostics::{Error, NamedSource};
|
||||
use oxc_linter::{
|
||||
Linter, ModuleRecord,
|
||||
loader::{JavaScriptSource, LINT_PARTIAL_LOADER_EXT, Loader},
|
||||
LINTABLE_EXTENSIONS, Linter, ModuleRecord,
|
||||
loader::{JavaScriptSource, Loader},
|
||||
};
|
||||
use oxc_parser::{ParseOptions, Parser};
|
||||
use oxc_semantic::SemanticBuilder;
|
||||
use oxc_span::VALID_EXTENSIONS;
|
||||
|
||||
use crate::DiagnosticReport;
|
||||
use crate::linter::error_with_position::{ErrorReport, ErrorWithPosition, FixedContent};
|
||||
@@ -187,9 +186,8 @@ impl IsolatedLintHandler {
|
||||
|
||||
fn should_lint_path(path: &Path) -> bool {
|
||||
static WANTED_EXTENSIONS: OnceLock<FxHashSet<&'static str>> = OnceLock::new();
|
||||
let wanted_exts = WANTED_EXTENSIONS.get_or_init(|| {
|
||||
VALID_EXTENSIONS.iter().chain(LINT_PARTIAL_LOADER_EXT.iter()).copied().collect()
|
||||
});
|
||||
let wanted_exts =
|
||||
WANTED_EXTENSIONS.get_or_init(|| LINTABLE_EXTENSIONS.iter().copied().collect());
|
||||
|
||||
path.extension()
|
||||
.and_then(std::ffi::OsStr::to_str)
|
||||
|
||||
@@ -90,4 +90,11 @@ mod test {
|
||||
Tester::new_with_linter(linter)
|
||||
.test_and_snapshot_single_file("fixtures/linter/regexp_feature/index.ts");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_frameworks() {
|
||||
Tester::new().test_and_snapshot_single_file("fixtures/linter/astro/debugger.astro");
|
||||
Tester::new().test_and_snapshot_single_file("fixtures/linter/vue/debugger.vue");
|
||||
Tester::new().test_and_snapshot_single_file("fixtures/linter/svelte/debugger.svelte");
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
---
|
||||
source: crates/oxc_language_server/src/linter/tester.rs
|
||||
---
|
||||
code: "eslint(no-debugger)"
|
||||
code_description.href: "https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-debugger"
|
||||
message: "`debugger` statement is not allowed\nhelp: Delete this code."
|
||||
range: Range { start: Position { line: 1, character: 0 }, end: Position { line: 1, character: 8 } }
|
||||
related_information[0].message: ""
|
||||
related_information[0].location.uri: "file://<variable>/fixtures/linter/astro/debugger.astro"
|
||||
related_information[0].location.range: Range { start: Position { line: 1, character: 0 }, end: Position { line: 1, character: 8 } }
|
||||
severity: Some(Warning)
|
||||
source: Some("oxc")
|
||||
tags: None
|
||||
|
||||
|
||||
code: "eslint(no-debugger)"
|
||||
code_description.href: "https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-debugger"
|
||||
message: "`debugger` statement is not allowed\nhelp: Delete this code."
|
||||
range: Range { start: Position { line: 10, character: 2 }, end: Position { line: 10, character: 10 } }
|
||||
related_information[0].message: ""
|
||||
related_information[0].location.uri: "file://<variable>/fixtures/linter/astro/debugger.astro"
|
||||
related_information[0].location.range: Range { start: Position { line: 10, character: 2 }, end: Position { line: 10, character: 10 } }
|
||||
severity: Some(Warning)
|
||||
source: Some("oxc")
|
||||
tags: None
|
||||
|
||||
|
||||
code: "eslint(no-debugger)"
|
||||
code_description.href: "https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-debugger"
|
||||
message: "`debugger` statement is not allowed\nhelp: Delete this code."
|
||||
range: Range { start: Position { line: 14, character: 2 }, end: Position { line: 14, character: 10 } }
|
||||
related_information[0].message: ""
|
||||
related_information[0].location.uri: "file://<variable>/fixtures/linter/astro/debugger.astro"
|
||||
related_information[0].location.range: Range { start: Position { line: 14, character: 2 }, end: Position { line: 14, character: 10 } }
|
||||
severity: Some(Warning)
|
||||
source: Some("oxc")
|
||||
tags: None
|
||||
|
||||
|
||||
code: "eslint(no-debugger)"
|
||||
code_description.href: "https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-debugger"
|
||||
message: "`debugger` statement is not allowed\nhelp: Delete this code."
|
||||
range: Range { start: Position { line: 18, character: 2 }, end: Position { line: 18, character: 10 } }
|
||||
related_information[0].message: ""
|
||||
related_information[0].location.uri: "file://<variable>/fixtures/linter/astro/debugger.astro"
|
||||
related_information[0].location.range: Range { start: Position { line: 18, character: 2 }, end: Position { line: 18, character: 10 } }
|
||||
severity: Some(Warning)
|
||||
source: Some("oxc")
|
||||
tags: None
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
---
|
||||
source: crates/oxc_language_server/src/linter/tester.rs
|
||||
---
|
||||
code: "eslint(no-debugger)"
|
||||
code_description.href: "https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-debugger"
|
||||
message: "`debugger` statement is not allowed\nhelp: Delete this code."
|
||||
range: Range { start: Position { line: 1, character: 1 }, end: Position { line: 1, character: 10 } }
|
||||
related_information[0].message: ""
|
||||
related_information[0].location.uri: "file://<variable>/fixtures/linter/svelte/debugger.svelte"
|
||||
related_information[0].location.range: Range { start: Position { line: 1, character: 1 }, end: Position { line: 1, character: 10 } }
|
||||
severity: Some(Warning)
|
||||
source: Some("oxc")
|
||||
tags: None
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
source: crates/oxc_language_server/src/linter/tester.rs
|
||||
---
|
||||
code: "eslint(no-debugger)"
|
||||
code_description.href: "https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-debugger"
|
||||
message: "`debugger` statement is not allowed\nhelp: Delete this code."
|
||||
range: Range { start: Position { line: 5, character: 4 }, end: Position { line: 5, character: 12 } }
|
||||
related_information[0].message: ""
|
||||
related_information[0].location.uri: "file://<variable>/fixtures/linter/vue/debugger.vue"
|
||||
related_information[0].location.range: Range { start: Position { line: 5, character: 4 }, end: Position { line: 5, character: 12 } }
|
||||
severity: Some(Warning)
|
||||
source: Some("oxc")
|
||||
tags: None
|
||||
|
||||
|
||||
code: "eslint(no-debugger)"
|
||||
code_description.href: "https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-debugger"
|
||||
message: "`debugger` statement is not allowed\nhelp: Delete this code."
|
||||
range: Range { start: Position { line: 10, character: 4 }, end: Position { line: 10, character: 13 } }
|
||||
related_information[0].message: ""
|
||||
related_information[0].location.uri: "file://<variable>/fixtures/linter/vue/debugger.vue"
|
||||
related_information[0].location.range: Range { start: Position { line: 10, character: 4 }, end: Position { line: 10, character: 13 } }
|
||||
severity: Some(Warning)
|
||||
source: Some("oxc")
|
||||
tags: None
|
||||
@@ -102,8 +102,10 @@ impl Tester<'_> {
|
||||
#[expect(clippy::disallowed_methods)]
|
||||
pub fn test_and_snapshot_single_file(&self, relative_file_path: &str) {
|
||||
let uri = get_file_uri(relative_file_path);
|
||||
let content = std::fs::read_to_string(uri.to_file_path().unwrap())
|
||||
.expect("could not read fixture file");
|
||||
let content = match std::fs::read_to_string(uri.to_file_path().unwrap()) {
|
||||
Ok(content) => content,
|
||||
Err(err) => panic!("could not read fixture file: {err}: {relative_file_path}"),
|
||||
};
|
||||
let reports = self.server_linter.run_single(&uri, Some(content)).unwrap();
|
||||
let snapshot = if reports.is_empty() {
|
||||
"No diagnostic reports".to_string()
|
||||
|
||||
@@ -41,6 +41,7 @@ oxc_syntax = { workspace = true, features = ["serialize"] }
|
||||
|
||||
#
|
||||
bitflags = { workspace = true }
|
||||
constcat = { workspace = true }
|
||||
convert_case = { workspace = true }
|
||||
cow-utils = { workspace = true }
|
||||
fast-glob = { workspace = true }
|
||||
|
||||
@@ -38,6 +38,7 @@ pub use crate::{
|
||||
context::LintContext,
|
||||
fixer::FixKind,
|
||||
frameworks::FrameworkFlags,
|
||||
loader::LINTABLE_EXTENSIONS,
|
||||
module_record::ModuleRecord,
|
||||
options::LintOptions,
|
||||
options::{AllowWarnDeny, InvalidFilterKind, LintFilter, LintFilterKind},
|
||||
|
||||
@@ -4,7 +4,7 @@ use oxc_span::SourceType;
|
||||
|
||||
mod partial_loader;
|
||||
mod source;
|
||||
pub use partial_loader::{LINT_PARTIAL_LOADER_EXT, PartialLoader};
|
||||
pub use partial_loader::{LINT_PARTIAL_LOADER_EXTENSIONS, LINTABLE_EXTENSIONS, PartialLoader};
|
||||
pub use source::JavaScriptSource;
|
||||
|
||||
// TODO: use oxc_resolver::FileSystem. We can't do so until that crate exposes FileSystemOs
|
||||
@@ -19,7 +19,7 @@ impl Loader {
|
||||
|| path
|
||||
.extension()
|
||||
.and_then(std::ffi::OsStr::to_str)
|
||||
.is_some_and(|ext| LINT_PARTIAL_LOADER_EXT.contains(&ext))
|
||||
.is_some_and(|ext| LINT_PARTIAL_LOADER_EXTENSIONS.contains(&ext))
|
||||
}
|
||||
|
||||
/// # Errors
|
||||
|
||||
@@ -2,13 +2,22 @@ mod astro;
|
||||
mod svelte;
|
||||
mod vue;
|
||||
|
||||
use oxc_span::VALID_EXTENSIONS;
|
||||
|
||||
pub use self::{astro::AstroPartialLoader, svelte::SveltePartialLoader, vue::VuePartialLoader};
|
||||
use crate::loader::JavaScriptSource;
|
||||
|
||||
const SCRIPT_START: &str = "<script";
|
||||
const SCRIPT_END: &str = "</script>";
|
||||
|
||||
pub const LINT_PARTIAL_LOADER_EXT: &[&str] = &["vue", "astro", "svelte"];
|
||||
/// File extensions that can contain JS/TS code in certain parts, such as in `<script>` tags, and can
|
||||
/// be loaded using the [`PartialLoader`].
|
||||
pub const LINT_PARTIAL_LOADER_EXTENSIONS: &[&str] = &["vue", "astro", "svelte"];
|
||||
|
||||
/// All valid JavaScript/TypeScript extensions, plus additional framework files that
|
||||
/// contain JavaScript/TypeScript code in them (e.g., Vue, Astro, Svelte, etc.).
|
||||
pub const LINTABLE_EXTENSIONS: &[&str] =
|
||||
constcat::concat_slices!([&str]: VALID_EXTENSIONS, LINT_PARTIAL_LOADER_EXTENSIONS);
|
||||
|
||||
pub struct PartialLoader;
|
||||
|
||||
|
||||
@@ -4,7 +4,10 @@ use oxc_macros::declare_oxc_lint;
|
||||
use oxc_span::Span;
|
||||
|
||||
use crate::{
|
||||
context::LintContext, loader::LINT_PARTIAL_LOADER_EXT, rule::Rule, utils::is_empty_stmt,
|
||||
context::{ContextHost, LintContext},
|
||||
loader::LINT_PARTIAL_LOADER_EXTENSIONS,
|
||||
rule::Rule,
|
||||
utils::is_empty_stmt,
|
||||
};
|
||||
|
||||
fn no_empty_file_diagnostic(span: Span) -> OxcDiagnostic {
|
||||
@@ -39,13 +42,6 @@ declare_oxc_lint!(
|
||||
|
||||
impl Rule for NoEmptyFile {
|
||||
fn run_once(&self, ctx: &LintContext) {
|
||||
if ctx
|
||||
.file_path()
|
||||
.extension()
|
||||
.is_some_and(|ext| LINT_PARTIAL_LOADER_EXT.contains(&ext.to_string_lossy().as_ref()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
let Some(root) = ctx.nodes().root_node() else {
|
||||
return;
|
||||
};
|
||||
@@ -67,6 +63,12 @@ impl Rule for NoEmptyFile {
|
||||
span.end = std::cmp::min(span.end, 100);
|
||||
ctx.diagnostic(no_empty_file_diagnostic(span));
|
||||
}
|
||||
|
||||
fn should_run(&self, ctx: &ContextHost) -> bool {
|
||||
ctx.file_path().extension().is_some_and(|ext| {
|
||||
!LINT_PARTIAL_LOADER_EXTENSIONS.contains(&ext.to_string_lossy().as_ref())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn has_triple_slash_directive(ctx: &LintContext<'_>) -> bool {
|
||||
|
||||
@@ -25,7 +25,7 @@ use oxc_span::{CompactStr, SourceType, VALID_EXTENSIONS};
|
||||
use super::LintServiceOptions;
|
||||
use crate::{
|
||||
Fixer, Linter, Message,
|
||||
loader::{JavaScriptSource, LINT_PARTIAL_LOADER_EXT, PartialLoader},
|
||||
loader::{JavaScriptSource, LINT_PARTIAL_LOADER_EXTENSIONS, PartialLoader},
|
||||
module_record::ModuleRecord,
|
||||
utils::read_to_string,
|
||||
};
|
||||
@@ -175,7 +175,7 @@ impl Runtime {
|
||||
) -> Option<Result<(SourceType, String), Error>> {
|
||||
let source_type = SourceType::from_path(path);
|
||||
let not_supported_yet =
|
||||
source_type.as_ref().is_err_and(|_| !LINT_PARTIAL_LOADER_EXT.contains(&ext));
|
||||
source_type.as_ref().is_err_and(|_| !LINT_PARTIAL_LOADER_EXTENSIONS.contains(&ext));
|
||||
if not_supported_yet {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ impl ContentEq for SourceType {
|
||||
}
|
||||
|
||||
/// Valid file extensions
|
||||
pub const VALID_EXTENSIONS: [&str; 8] = ["js", "mjs", "cjs", "jsx", "ts", "mts", "cts", "tsx"];
|
||||
pub const VALID_EXTENSIONS: &[&str] = &["js", "mjs", "cjs", "jsx", "ts", "mts", "cts", "tsx"];
|
||||
|
||||
impl SourceType {
|
||||
/// Creates a [`SourceType`] representing a regular [`JavaScript`] file.
|
||||
|
||||
Reference in new issue
Block a user