IWANABETHATGUY 2024-01-08 11:38:25 +08:00 committed by GitHub
parent c6eb519417
commit fe48bfae0c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 114 additions and 88 deletions

View file

@ -10,7 +10,13 @@ use crate::options::LintOptions;
use miette::NamedSource;
use oxc_allocator::Allocator;
use oxc_diagnostics::{miette, Error, Severity};
use oxc_linter::{partial_loader::LINT_PARTIAL_LOADER_EXT, LintContext, LintSettings, Linter};
use oxc_linter::{
partial_loader::{
AstroPartialLoader, JavaScriptSource, SveltePartialLoader, VuePartialLoader,
LINT_PARTIAL_LOADER_EXT,
},
LintContext, LintSettings, Linter,
};
use oxc_linter_plugin::{make_relative_path_parts, LinterPlugin};
use oxc_parser::Parser;
use oxc_semantic::SemanticBuilder;
@ -37,14 +43,23 @@ struct LabeledSpanWithPosition {
}
impl ErrorWithPosition {
pub fn new(error: Error, text: &str, fixed_content: Option<FixedContent>) -> Self {
pub fn new(
error: Error,
text: &str,
fixed_content: Option<FixedContent>,
start: usize,
) -> Self {
let labels = error.labels().map_or(vec![], Iterator::collect);
let labels_with_pos: Vec<LabeledSpanWithPosition> = labels
.iter()
.map(|labeled_span| LabeledSpanWithPosition {
start_pos: offset_to_position(labeled_span.offset(), text).unwrap_or_default(),
end_pos: offset_to_position(labeled_span.offset() + labeled_span.len(), text)
start_pos: offset_to_position(labeled_span.offset() + start, text)
.unwrap_or_default(),
end_pos: offset_to_position(
labeled_span.offset() + start + labeled_span.len(),
text,
)
.unwrap_or_default(),
message: labeled_span.label().map(ToString::to_string),
})
.collect();
@ -161,8 +176,9 @@ impl IsolatedLintHandler {
path: &Path,
content: Option<String>,
) -> Option<Vec<DiagnosticReport>> {
debug!("run single {path:?}");
if Self::is_wanted_ext(path) {
Some(Self::lint_path(&self.linter, path, Arc::clone(&self.plugin), content).map_or(
Some(Self::lint_path(&self.linter, path, &Arc::clone(&self.plugin), content).map_or(
vec![],
|(p, errors)| {
let mut diagnostics: Vec<DiagnosticReport> =
@ -211,7 +227,7 @@ impl IsolatedLintHandler {
}
fn is_wanted_ext(path: &Path) -> bool {
let extensions = get_extensions();
let extensions = get_valid_extensions();
path.extension().map_or(false, |ext| extensions.contains(&ext.to_string_lossy().as_ref()))
}
@ -237,115 +253,117 @@ impl IsolatedLintHandler {
}
fn may_need_extract_js_content<'a>(
_source_text: &'a str,
_ext: &str,
) -> Option<(&'a str, SourceType)> {
None
// match ext {
// "vue" => PartialLoader::Vue.build(source_text),
// "astro" => PartialLoader::Astro.build(source_text)),
// _ => None,
// }
source_text: &'a str,
ext: &str,
) -> Option<Vec<JavaScriptSource<'a>>> {
match ext {
"vue" => Some(VuePartialLoader::new(source_text).parse()),
"astro" => Some(AstroPartialLoader::new(source_text).parse()),
"svelte" => Some(SveltePartialLoader::new(source_text).parse()),
_ => None,
}
}
fn lint_path(
linter: &Linter,
path: &Path,
plugin: Plugin,
plugin: &Plugin,
source_text: Option<String>,
) -> Option<(PathBuf, Vec<ErrorWithPosition>)> {
let ext = path.extension().and_then(std::ffi::OsStr::to_str)?;
let (source_type, source_text) = Self::get_source_type_and_text(path, source_text, ext)?;
let (source_text, source_type) = Self::may_need_extract_js_content(&source_text, ext)
.unwrap_or((&source_text, source_type));
let (source_type, original_source_text) =
Self::get_source_type_and_text(path, source_text, ext)?;
let javascript_sources = Self::may_need_extract_js_content(&original_source_text, ext)
.unwrap_or_else(|| {
vec![JavaScriptSource { source_text: &original_source_text, source_type, start: 0 }]
});
debug!("lint {path:?}");
let mut diagnostics = vec![];
for source in javascript_sources {
let JavaScriptSource { source_text: javascript_source_text, source_type, start } =
source;
let allocator = Allocator::default();
let ret = Parser::new(&allocator, javascript_source_text, source_type)
.allow_return_outside_function(true)
.parse();
let allocator = Allocator::default();
let ret = Parser::new(&allocator, source_text, source_type)
.allow_return_outside_function(true)
.parse();
if !ret.errors.is_empty() {
let reports = ret
.errors
.into_iter()
.map(|diagnostic| ErrorReport { error: diagnostic, fixed_content: None })
.collect();
return Some(Self::wrap_diagnostics(path, &original_source_text, reports, start));
};
if !ret.errors.is_empty() {
let reports = ret
.errors
.into_iter()
.map(|diagnostic| ErrorReport { error: diagnostic, fixed_content: None })
.collect();
let program = allocator.alloc(ret.program);
let semantic_ret = SemanticBuilder::new(javascript_source_text, source_type)
.with_trivias(ret.trivias)
.with_check_syntax_error(true)
.build(program);
return Some(Self::wrap_diagnostics(path, source_text, reports));
};
if !semantic_ret.errors.is_empty() {
let reports = semantic_ret
.errors
.into_iter()
.map(|diagnostic| ErrorReport { error: diagnostic, fixed_content: None })
.collect();
return Some(Self::wrap_diagnostics(path, &original_source_text, reports, start));
};
let program = allocator.alloc(ret.program);
let semantic_ret = SemanticBuilder::new(source_text, source_type)
.with_trivias(ret.trivias)
.with_check_syntax_error(true)
.build(program);
if !semantic_ret.errors.is_empty() {
let reports = semantic_ret
.errors
.into_iter()
.map(|diagnostic| ErrorReport { error: diagnostic, fixed_content: None })
.collect();
return Some(Self::wrap_diagnostics(path, source_text, reports));
};
let mut lint_ctx = LintContext::new(
path.to_path_buf().into_boxed_path(),
&Rc::new(semantic_ret.semantic),
LintSettings::default(),
);
{
if let Ok(guard) = plugin.read() {
if let Some(plugin) = &*guard {
plugin
.lint_file(&mut lint_ctx, make_relative_path_parts(&path.into()))
.unwrap();
let mut lint_ctx = LintContext::new(
path.to_path_buf().into_boxed_path(),
&Rc::new(semantic_ret.semantic),
LintSettings::default(),
);
{
if let Ok(guard) = plugin.read() {
if let Some(plugin) = &*guard {
plugin
.lint_file(&mut lint_ctx, make_relative_path_parts(&path.into()))
.unwrap();
}
}
}
}
drop(plugin); // explicitly drop plugin so that we consume the plugin in this function's body
let result = linter.run(lint_ctx);
let result = linter.run(lint_ctx);
if result.is_empty() {
return None;
}
if linter.options().fix {
let reports = result
.into_iter()
.map(|msg| {
let fixed_content = msg.fix.map(|f| FixedContent {
code: f.content.to_string(),
range: Range {
start: offset_to_position(f.span.start as usize, source_text)
.unwrap_or_default(),
end: offset_to_position(f.span.end as usize, source_text)
.unwrap_or_default(),
start: offset_to_position(
f.span.start as usize + start,
javascript_source_text,
)
.unwrap_or_default(),
end: offset_to_position(
f.span.end as usize + start,
javascript_source_text,
)
.unwrap_or_default(),
},
});
ErrorReport { error: msg.error, fixed_content }
})
.collect::<Vec<ErrorReport>>();
return Some(Self::wrap_diagnostics(path, source_text, reports));
let (_, errors_with_position) =
Self::wrap_diagnostics(path, &original_source_text, reports, start);
diagnostics.extend(errors_with_position);
}
let errors = result
.into_iter()
.map(|diagnostic| ErrorReport { error: diagnostic.error, fixed_content: None })
.collect();
Some(Self::wrap_diagnostics(path, source_text, errors))
Some((path.to_path_buf(), diagnostics))
}
fn wrap_diagnostics(
path: &Path,
source_text: &str,
reports: Vec<ErrorReport>,
start: usize,
) -> (PathBuf, Vec<ErrorWithPosition>) {
let source = Arc::new(NamedSource::new(path.to_string_lossy(), source_text.to_owned()));
let diagnostics = reports
@ -355,6 +373,7 @@ impl IsolatedLintHandler {
report.error.with_source_code(Arc::clone(&source)),
source_text,
report.fixed_content,
start,
)
})
.collect();
@ -362,7 +381,7 @@ impl IsolatedLintHandler {
}
}
fn get_extensions() -> Vec<&'static str> {
fn get_valid_extensions() -> Vec<&'static str> {
VALID_EXTENSIONS
.iter()
.chain(LINT_PARTIAL_LOADER_EXT.iter())

View file

@ -323,7 +323,7 @@ impl Backend {
.await;
}
async fn handle_file_update(&self, uri: Url, content: Option<String>, _version: Option<i32>) {
async fn handle_file_update(&self, uri: Url, content: Option<String>, version: Option<i32>) {
if let Some(Some(root_uri)) = self.root_uri.get() {
self.server_linter.make_plugin(root_uri);
if let Some(diagnostics) = self.server_linter.run_single(root_uri, &uri, content) {
@ -331,7 +331,7 @@ impl Backend {
.publish_diagnostics(
uri.clone(),
diagnostics.clone().into_iter().map(|d| d.diagnostic).collect(),
None,
version,
)
.await;

View file

@ -43,6 +43,7 @@ impl<'a> AstroPartialLoader<'a> {
Some(JavaScriptSource::new(
js_code,
SourceType::default().with_typescript(true).with_module(true),
start as usize,
))
}
@ -81,6 +82,7 @@ impl<'a> AstroPartialLoader<'a> {
results.push(JavaScriptSource::new(
&self.source_text[js_start..js_end],
SourceType::default().with_typescript(true).with_module(true),
js_start,
));
}
results

View file

@ -15,11 +15,14 @@ pub const LINT_PARTIAL_LOADER_EXT: &[&str] = &["vue", "astro", "svelte"];
pub struct JavaScriptSource<'a> {
pub source_text: &'a str,
pub source_type: SourceType,
/// The javascript source could be embedded in some file,
/// use `start` to record start offset of js block in the original file.
pub start: usize,
}
impl<'a> JavaScriptSource<'a> {
pub fn new(source_text: &'a str, source_type: SourceType) -> Self {
Self { source_text, source_type }
pub fn new(source_text: &'a str, source_type: SourceType, start: usize) -> Self {
Self { source_text, source_type, start }
}
}

View file

@ -43,6 +43,6 @@ impl<'a> SveltePartialLoader<'a> {
let source_text = &self.source_text[js_start..js_end];
let source_type = SourceType::default().with_module(true).with_typescript(is_ts);
Some(JavaScriptSource::new(source_text, source_type))
Some(JavaScriptSource::new(source_text, source_type, js_start))
}
}

View file

@ -55,7 +55,7 @@ impl<'a> VuePartialLoader<'a> {
let source_text = &self.source_text[js_start..js_end];
let source_type =
SourceType::default().with_module(true).with_typescript(is_ts).with_jsx(is_jsx);
Some(JavaScriptSource::new(source_text, source_type))
Some(JavaScriptSource::new(source_text, source_type, js_start))
}
}

View file

@ -176,13 +176,13 @@ impl Runtime {
};
let sources = PartialLoader::parse(ext, &source_text)
.unwrap_or_else(|| vec![JavaScriptSource::new(&source_text, source_type)]);
.unwrap_or_else(|| vec![JavaScriptSource::new(&source_text, source_type, 0)]);
if sources.is_empty() {
return;
}
for JavaScriptSource { source_text, source_type } in sources {
for JavaScriptSource { source_text, source_type, .. } in sources {
let allocator = Allocator::default();
let mut messages =
self.process_source(path, &allocator, source_text, source_type, true, tx_error);

View file

@ -127,6 +127,7 @@ export async function activate(context: ExtensionContext) {
"typescriptreact",
"javascriptreact",
"vue",
"svelte",
].map((lang) => ({
language: lang,
scheme: "file",

View file

@ -28,11 +28,12 @@
"url": "https://github.com/sponsors/boshen"
},
"activationEvents": [
"onStartupFinished",
"onLanguage:javascript",
"onLanguage:javascriptreact",
"onLanguage:typescript",
"onLanguage:typescriptreact"
"onLanguage:typescriptreact",
"onLanguage:vue",
"onLanguage:svelte"
],
"main": "./out/main.js",
"contributes": {