Skip to content

Commit b220da5

Browse files
Dunqingcamchenry
authored andcommitted
feat(transformer, minifier, syntax)!: remove ESTarget::ES5 (#12448)
* close #11296 Both `Transformer` and `Minifier` have a minimum ES version support of `ES2015`. However, we can set the `ESTarget` to `ES5` now, which might mislead users into thinking we already support `ES5`, so for now, let's remove the `ESTarget::ES5` option to eliminate the confusion. Suggested by @shulaoda
1 parent bc1d716 commit b220da5

File tree

20 files changed

+1196
-505
lines changed

20 files changed

+1196
-505
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ jobs:
2020
runs-on: ubuntu-latest
2121
steps:
2222
- uses: taiki-e/checkout-action@b13d20b7cda4e2f325ef19895128f7ff735c0b3d # v1.3.1
23+
- uses: oxc-project/setup-node@f42e3bda950c7454575e78ee4eaac880a077700c # v1.0.0
2324
- uses: oxc-project/setup-rust@cd82e1efec7fef815e2c23d296756f31c7cdc03d # v1.0.0
2425
with:
2526
save-cache: ${{ github.ref_name == 'main' }}
@@ -48,6 +49,7 @@ jobs:
4849
if: ${{ github.ref_name == 'main' }}
4950
steps:
5051
- uses: taiki-e/checkout-action@b13d20b7cda4e2f325ef19895128f7ff735c0b3d # v1.3.1
52+
- uses: oxc-project/setup-node@f42e3bda950c7454575e78ee4eaac880a077700c # v1.0.0
5153
- uses: oxc-project/setup-rust@cd82e1efec7fef815e2c23d296756f31c7cdc03d # v1.0.0
5254
with:
5355
save-cache: ${{ github.ref_name == 'main' }}
@@ -65,6 +67,7 @@ jobs:
6567
# Unsung heros of the internet, who led me here to speed up window's slowness:
6668
# https://github.com/actions/cache/issues/752#issuecomment-1847036770
6769
# https://github.com/astral-sh/uv/blob/502e04200d52de30d3159894833b3db4f0d6644d/.github/workflows/ci.yml#L158
70+
- uses: oxc-project/setup-node@f42e3bda950c7454575e78ee4eaac880a077700c # v1.0.0
6871
- uses: samypr100/setup-dev-drive@750bec535eb7e4833d6a4c86c5738751f9887575 # v3.4.2
6972
with:
7073
workspace-copy: true
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"categories": {
3+
"correctness": "off"
4+
},
5+
"rules": {
6+
"typescript/no-floating-promises": "error"
7+
}
8+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
const promise = new Promise((resolve, _reject) => resolve("value"));
2+
promise;
3+
4+
async function returnsPromise() {
5+
return "value";
6+
}
7+
8+
returnsPromise().then(() => {});
9+
10+
Promise.reject("value").catch();
11+
12+
Promise.reject("value").finally();
13+
14+
[1, 2, 3].map(async (x) => x + 1);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"compilerOptions": {
3+
"target": "es2016",
4+
"lib": ["ES2024"],
5+
"module": "commonjs",
6+
"esModuleInterop": true,
7+
"forceConsistentCasingInFileNames": true,
8+
"strict": true,
9+
"skipLibCheck": true
10+
}
11+
}

apps/oxlint/src/command/lint.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ pub struct LintCommand {
4646
#[bpaf(switch, hide_usage)]
4747
pub disable_nested_config: bool,
4848

49+
/// Enables rules that require type information.
50+
#[bpaf(switch, hide_usage)]
51+
pub type_aware: bool,
52+
4953
#[bpaf(external)]
5054
pub inline_config_options: InlineConfigOptions,
5155

@@ -573,6 +577,14 @@ mod lint_options {
573577
let options = get_lint_options(".");
574578
assert!(!options.disable_nested_config);
575579
}
580+
581+
#[test]
582+
fn type_aware() {
583+
let options = get_lint_options("--type-aware");
584+
assert!(options.type_aware);
585+
let options = get_lint_options(".");
586+
assert!(!options.type_aware);
587+
}
576588
}
577589

578590
#[cfg(test)]

apps/oxlint/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ mod lint;
1010
mod output_formatter;
1111
mod result;
1212
mod tester;
13+
mod tsgolint;
1314
mod walk;
1415

1516
pub mod cli {

apps/oxlint/src/lint.rs

Lines changed: 186 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,24 @@ use std::{
1111
use cow_utils::CowUtils;
1212
use ignore::{gitignore::Gitignore, overrides::OverrideBuilder};
1313
use oxc_allocator::AllocatorPool;
14-
use oxc_diagnostics::{DiagnosticSender, DiagnosticService, GraphicalReportHandler, OxcDiagnostic};
14+
use oxc_diagnostics::{
15+
DiagnosticSender, DiagnosticService, GraphicalReportHandler, OxcDiagnostic, Severity,
16+
};
1517
use oxc_linter::{
1618
AllowWarnDeny, Config, ConfigStore, ConfigStoreBuilder, ExternalLinter, ExternalPluginStore,
1719
InvalidFilterKind, LintFilter, LintOptions, LintService, LintServiceOptions, Linter, Oxlintrc,
20+
ResolvedLinterState, read_to_string,
1821
};
1922
use rustc_hash::{FxHashMap, FxHashSet};
2023
use serde_json::Value;
2124

2225
use crate::{
2326
cli::{CliRunResult, LintCommand, MiscOptions, ReportUnusedDirectives, WarningOptions},
2427
output_formatter::{LintCommandInfo, OutputFormatter},
28+
tsgolint::{
29+
MessageType, TsGoLintInput, TsGoLintInputFile, TsGoLintState, parse_tsgolint_output,
30+
try_find_tsgolint_executable,
31+
},
2532
walk::Walk,
2633
};
2734

@@ -290,19 +297,47 @@ impl LintRunner {
290297
}
291298
};
292299

300+
// Enable type-aware linting
301+
// TODO: Add a warning message if `tsgolint` cannot be found, but type-aware rules are enabled
302+
let type_aware = self.options.type_aware;
303+
293304
let report_unused_directives = match inline_config_options.report_unused_directives {
294305
ReportUnusedDirectives::WithoutSeverity(true) => Some(AllowWarnDeny::Warn),
295306
ReportUnusedDirectives::WithSeverity(Some(severity)) => Some(severity),
296307
_ => None,
297308
};
309+
let (mut diagnostic_service, tx_error) =
310+
Self::get_diagnostic_service(&output_formatter, &warning_options, &misc_options);
298311

299-
let linter = Linter::new(
300-
LintOptions::default(),
301-
ConfigStore::new(lint_config, nested_configs, external_plugin_store),
302-
self.external_linter,
303-
)
304-
.with_fix(fix_options.fix_kind())
305-
.with_report_unused_directives(report_unused_directives);
312+
let config_store = ConfigStore::new(lint_config, nested_configs, external_plugin_store);
313+
314+
let tsgolint_state = if type_aware {
315+
Some(TsGoLintState {
316+
error_sender: tx_error.clone(),
317+
config_store: config_store.clone(),
318+
executable_path: try_find_tsgolint_executable(options.cwd())
319+
.unwrap_or(PathBuf::from("tsgolint")),
320+
cwd: options.cwd().to_path_buf(),
321+
paths: paths.clone(),
322+
rules: config_store
323+
.rules()
324+
.iter()
325+
.filter_map(|(rule, status)| {
326+
if status.is_warn_deny() && rule.is_tsgolint_rule() {
327+
Some((*status, rule.clone()))
328+
} else {
329+
None
330+
}
331+
})
332+
.collect(),
333+
})
334+
} else {
335+
None
336+
};
337+
338+
let linter = Linter::new(LintOptions::default(), config_store, self.external_linter)
339+
.with_fix(fix_options.fix_kind())
340+
.with_report_unused_directives(report_unused_directives);
306341

307342
let tsconfig = basic_options.tsconfig;
308343
if let Some(path) = tsconfig.as_ref() {
@@ -323,13 +358,147 @@ impl LintRunner {
323358
}
324359
}
325360

326-
let (mut diagnostic_service, tx_error) =
327-
Self::get_diagnostic_service(&output_formatter, &warning_options, &misc_options);
328-
329361
let number_of_rules = linter.number_of_rules();
330362

331363
let allocator_pool = AllocatorPool::new(rayon::current_num_threads());
332364

365+
if type_aware
366+
&& let Some(tsgolint_state) = tsgolint_state
367+
&& !tsgolint_state.rules.is_empty()
368+
&& !tsgolint_state.paths.is_empty()
369+
{
370+
// Feed JSON into STDIN of tsgolint in this format:
371+
// ```
372+
// {
373+
// "files": [
374+
// {
375+
// "file_path": "/absolute/path/to/file.ts",
376+
// "rules": ["rule-1", "another-rule"]
377+
// }
378+
// ]
379+
// }
380+
// ```
381+
let json_input = TsGoLintInput {
382+
files: tsgolint_state
383+
.paths
384+
.iter()
385+
.map(|path| TsGoLintInputFile {
386+
file_path: path.to_string_lossy().to_string(),
387+
rules: tsgolint_state
388+
.rules
389+
.iter()
390+
.map(|(_, r)| r.name().to_owned())
391+
.collect(),
392+
})
393+
.collect(),
394+
};
395+
396+
let handler = std::thread::spawn(move || {
397+
let child = std::process::Command::new(tsgolint_state.executable_path)
398+
.arg("headless")
399+
.stdin(std::process::Stdio::piped())
400+
.stdout(std::process::Stdio::piped())
401+
.spawn();
402+
403+
let Ok(mut child) = child else {
404+
// For now, silently ignore errors if `tsgolint` does not appear to be installed, or cannot
405+
// be spawned correctly.
406+
return Ok(());
407+
};
408+
409+
let mut stdin = child.stdin.take().expect("Failed to open tsgolint stdin");
410+
411+
std::thread::spawn(move || {
412+
let json =
413+
serde_json::to_string(&json_input).expect("Failed to serialize JSON");
414+
415+
stdin.write_all(json.as_bytes()).expect("Failed to write to tsgolint stdin");
416+
});
417+
418+
// TODO: Stream diagnostics as they are emitted, rather than waiting
419+
let output = child.wait_with_output().expect("Failed to wait for tsgolint process");
420+
421+
match parse_tsgolint_output(&output.stdout) {
422+
Ok(parsed) => {
423+
let mut resolved_configs: FxHashMap<PathBuf, ResolvedLinterState> =
424+
FxHashMap::default();
425+
let mut severities: FxHashMap<String, Option<AllowWarnDeny>> =
426+
FxHashMap::default();
427+
428+
let mut oxc_diagnostics: FxHashMap<PathBuf, Vec<OxcDiagnostic>> =
429+
FxHashMap::default();
430+
for tsgolint_diagnostic in parsed {
431+
// For now, ignore any `tsgolint` errors.
432+
if tsgolint_diagnostic.r#type == MessageType::Error {
433+
continue;
434+
}
435+
436+
let path = tsgolint_diagnostic.file_path.clone();
437+
let resolved_config = resolved_configs
438+
.entry(path.clone())
439+
.or_insert_with(|| tsgolint_state.config_store.resolve(&path));
440+
441+
let rule = tsgolint_diagnostic.rule.clone();
442+
let severity = severities.entry(rule).or_insert_with(|| {
443+
resolved_config.rules.iter().find_map(|(rule, status)| {
444+
if rule.name() == tsgolint_diagnostic.rule {
445+
Some(*status)
446+
} else {
447+
None
448+
}
449+
})
450+
});
451+
452+
let oxc_diagnostic: OxcDiagnostic = tsgolint_diagnostic.into();
453+
let Some(severity) = severity else {
454+
// If the severity is not found, we should not report the diagnostic
455+
continue;
456+
};
457+
let oxc_diagnostic =
458+
oxc_diagnostic.with_severity(if *severity == AllowWarnDeny::Deny {
459+
Severity::Error
460+
} else {
461+
Severity::Warning
462+
});
463+
464+
oxc_diagnostics.entry(path.clone()).or_default().push(oxc_diagnostic);
465+
}
466+
467+
for (file_path, diagnostics) in oxc_diagnostics {
468+
let diagnostics = DiagnosticService::wrap_diagnostics(
469+
tsgolint_state.cwd.clone(),
470+
file_path.clone(),
471+
&read_to_string(&file_path).unwrap_or_else(|_| String::new()),
472+
diagnostics,
473+
);
474+
tsgolint_state
475+
.error_sender
476+
.send((file_path.clone(), diagnostics))
477+
.expect("Failed to send diagnostic");
478+
}
479+
480+
Ok(())
481+
}
482+
483+
Err(err) => Err(format!("Failed to parse tsgolint output: {err}")),
484+
}
485+
});
486+
487+
match handler.join() {
488+
Ok(Ok(())) => {
489+
// Successfully ran tsgolint
490+
}
491+
Ok(Err(err)) => {
492+
print_and_flush_stdout(stdout, &format!("Error running tsgolint: {err:?}"));
493+
return CliRunResult::TsGoLintError;
494+
}
495+
Err(err) => {
496+
print_and_flush_stdout(stdout, &format!("Error running tsgolint: {err:?}"));
497+
return CliRunResult::TsGoLintError;
498+
}
499+
}
500+
}
501+
333502
// Spawn linting in another thread so diagnostics can be printed immediately from diagnostic_service.run.
334503
rayon::spawn(move || {
335504
let mut lint_service = LintService::new(linter, allocator_pool, options);
@@ -1203,4 +1372,10 @@ mod test {
12031372
let args = &["-c", ".oxlintrc.json"];
12041373
Tester::new().with_cwd("fixtures/issue_11644".into()).test_and_snapshot(args);
12051374
}
1375+
1376+
#[test]
1377+
fn test_tsgolint() {
1378+
let args = &["fixtures/tsgolint", "--type-aware"];
1379+
Tester::new().test_and_snapshot(args);
1380+
}
12061381
}

apps/oxlint/src/result.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ pub enum CliRunResult {
1616
PrintConfigResult,
1717
ConfigFileInitFailed,
1818
ConfigFileInitSucceeded,
19+
TsGoLintError,
1920
}
2021

2122
impl Termination for CliRunResult {
@@ -35,7 +36,8 @@ impl Termination for CliRunResult {
3536
| Self::InvalidOptionTsConfig
3637
| Self::InvalidOptionSeverityWithoutFilter
3738
| Self::InvalidOptionSeverityWithoutPluginName
38-
| Self::InvalidOptionSeverityWithoutRuleName => ExitCode::FAILURE,
39+
| Self::InvalidOptionSeverityWithoutRuleName
40+
| Self::TsGoLintError => ExitCode::FAILURE,
3941
}
4042
}
4143
}

0 commit comments

Comments
 (0)