Skip to content

Commit 8139ad0

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 977d3ba commit 8139ad0

File tree

60 files changed

+735
-67
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+735
-67
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ jobs:
3030
runs-on: ubuntu-latest
3131
steps:
3232
- uses: taiki-e/checkout-action@b13d20b7cda4e2f325ef19895128f7ff735c0b3d # v1.3.1
33+
- uses: oxc-project/setup-node@f42e3bda950c7454575e78ee4eaac880a077700c # v1.0.0
3334
- uses: oxc-project/setup-rust@cd82e1efec7fef815e2c23d296756f31c7cdc03d # v1.0.0
3435
with:
3536
save-cache: ${{ github.ref_name == 'main' }}
@@ -45,6 +46,7 @@ jobs:
4546
if: ${{ github.ref_name == 'main' }}
4647
steps:
4748
- uses: taiki-e/checkout-action@b13d20b7cda4e2f325ef19895128f7ff735c0b3d # v1.3.1
49+
- uses: oxc-project/setup-node@f42e3bda950c7454575e78ee4eaac880a077700c # v1.0.0
4850
- uses: oxc-project/setup-rust@cd82e1efec7fef815e2c23d296756f31c7cdc03d # v1.0.0
4951
with:
5052
save-cache: ${{ github.ref_name == 'main' }}
@@ -62,6 +64,7 @@ jobs:
6264
# Unsung heros of the internet, who led me here to speed up window's slowness:
6365
# https://github.com/actions/cache/issues/752#issuecomment-1847036770
6466
# https://github.com/astral-sh/uv/blob/502e04200d52de30d3159894833b3db4f0d6644d/.github/workflows/ci.yml#L158
67+
- uses: oxc-project/setup-node@f42e3bda950c7454575e78ee4eaac880a077700c # v1.0.0
6568
- uses: samypr100/setup-dev-drive@750bec535eb7e4833d6a4c86c5738751f9887575 # v3.4.2
6669
with:
6770
workspace-copy: true

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ target/
77

88
# node_modules
99
/node_modules/
10+
/apps/oxlint/node_modules/
1011
/website/node_modules/
1112
/benchmark/node_modules/
1213
/tasks/benchmark/codspeed/node_modules/
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/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ mod output_formatter;
44
mod result;
55
mod runner;
66
mod tester;
7+
mod tsgolint;
78
mod walk;
89

910
pub mod cli {

apps/oxlint/src/lint.rs

Lines changed: 205 additions & 8 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+
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, Runner, 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

@@ -280,19 +287,43 @@ impl Runner for LintRunner {
280287

281288
let lint_config = config_builder.build();
282289

290+
// Enable type-aware linting if `--tsconfig` is passed
291+
// TODO: Add a warning message if `tsgolint` cannot be found, but type-aware rules are enabled
292+
let tsgolint_path =
293+
try_find_tsgolint_executable(options.cwd()).unwrap_or(PathBuf::from("tsgolint"));
294+
let type_aware = basic_options.tsconfig.is_some();
295+
296+
let tsgolint_state = if type_aware {
297+
Some(TsGoLintState {
298+
cwd: options.cwd().to_path_buf(),
299+
paths: paths.clone(),
300+
rules: lint_config
301+
.rules()
302+
.iter()
303+
.filter_map(|(rule, status)| {
304+
if status.is_warn_deny() && rule.is_tsgolint_rule() {
305+
Some((*status, rule.clone()))
306+
} else {
307+
None
308+
}
309+
})
310+
.collect(),
311+
})
312+
} else {
313+
None
314+
};
315+
283316
let report_unused_directives = match inline_config_options.report_unused_directives {
284317
ReportUnusedDirectives::WithoutSeverity(true) => Some(AllowWarnDeny::Warn),
285318
ReportUnusedDirectives::WithSeverity(Some(severity)) => Some(severity),
286319
_ => None,
287320
};
288321

289-
let linter = Linter::new(
290-
LintOptions::default(),
291-
ConfigStore::new(lint_config, nested_configs, external_plugin_store),
292-
self.external_linter,
293-
)
294-
.with_fix(fix_options.fix_kind())
295-
.with_report_unused_directives(report_unused_directives);
322+
let config_store = ConfigStore::new(lint_config, nested_configs, external_plugin_store);
323+
let tsgolint_config_store = if type_aware { Some(config_store.clone()) } else { None };
324+
let linter = Linter::new(LintOptions::default(), config_store, self.external_linter)
325+
.with_fix(fix_options.fix_kind())
326+
.with_report_unused_directives(report_unused_directives);
296327

297328
let tsconfig = basic_options.tsconfig;
298329
if let Some(path) = tsconfig.as_ref() {
@@ -315,11 +346,171 @@ impl Runner for LintRunner {
315346

316347
let (mut diagnostic_service, tx_error) =
317348
Self::get_diagnostic_service(&output_formatter, &warning_options, &misc_options);
349+
let tsgolint_error_sender = if type_aware { Some(tx_error.clone()) } else { None };
318350

319351
let number_of_rules = linter.number_of_rules();
320352

321353
let allocator_pool = AllocatorPool::new(rayon::current_num_threads());
322354

355+
if type_aware
356+
&& let Some(tsgolint_state) = tsgolint_state
357+
&& let Some(tsgolint_config_store) = tsgolint_config_store
358+
&& let Some(tsconfig) = tsconfig
359+
&& let Some(tsgolint_error_sender) = tsgolint_error_sender
360+
&& tsgolint_state.rules.len() > 0
361+
&& tsgolint_state.paths.len() > 0
362+
{
363+
// Feed JSON into STDIN of tsgolint in this format:
364+
// ```
365+
// {
366+
// "files": [
367+
// {
368+
// "file_path": "/absolute/path/to/file.ts",
369+
// "rules": ["rule-1", "another-rule"]
370+
// }
371+
// ]
372+
// }
373+
// ```
374+
let json_input = TsGoLintInput {
375+
files: tsgolint_state
376+
.paths
377+
.iter()
378+
.map(|path| TsGoLintInputFile {
379+
file_path: path.to_string_lossy().to_string(),
380+
rules: tsgolint_state
381+
.rules
382+
.iter()
383+
.map(|(_, r)| r.name().to_owned())
384+
.collect(),
385+
})
386+
.collect(),
387+
};
388+
389+
let handler = std::thread::spawn(move || {
390+
// `./tsgolint headless --tsconfig /path/to/project/tsconfig.json --cwd /path/to/project
391+
let child = std::process::Command::new(tsgolint_path)
392+
.arg("headless")
393+
.arg("--tsconfig")
394+
.arg(tsconfig)
395+
.arg("--cwd")
396+
.arg(&tsgolint_state.cwd)
397+
.stdin(std::process::Stdio::piped())
398+
.stdout(std::process::Stdio::piped())
399+
.spawn();
400+
401+
let Ok(mut child) = child else {
402+
// For now, silently ignore errors if `tsgolint` does not appear to be installed, or cannot
403+
// be spawned correctly.
404+
return Ok(());
405+
};
406+
407+
let mut stdin = child.stdin.take().expect("Failed to open tsgolint stdin");
408+
409+
std::thread::spawn(move || {
410+
let json =
411+
serde_json::to_string(&json_input).expect("Failed to serialize JSON");
412+
413+
stdin.write_all(json.as_bytes()).expect("Failed to write to tsgolint stdin");
414+
});
415+
416+
// TODO: Stream diagnostics as they are emitted, rather than waiting
417+
let output = child.wait_with_output().expect("Failed to wait for tsgolint process");
418+
419+
match parse_tsgolint_output(&output.stdout) {
420+
Ok(parsed) => {
421+
let oxc_diagnostics = parsed
422+
.into_iter()
423+
.filter_map(|tsgolint_diagnostic| {
424+
// For now, ignore any `tsgolint` errors.
425+
if tsgolint_diagnostic.r#type == MessageType::Error {
426+
return None;
427+
}
428+
// Resolve this config if not already stored
429+
// TODO(perf): do not re-resolve the same file path multiple times, instead cache the config
430+
let path = tsgolint_diagnostic.file_path.clone();
431+
let resolved_config = tsgolint_config_store.resolve(&path);
432+
// TODO(perf): build a map of rule->severity, rather than doing a linear search
433+
let severity =
434+
resolved_config.rules.iter().find_map(|(rule, status)| {
435+
if rule.name() == tsgolint_diagnostic.rule {
436+
Some((*status, rule))
437+
} else {
438+
None
439+
}
440+
});
441+
442+
let oxc_diagnostic: OxcDiagnostic = tsgolint_diagnostic.into();
443+
if severity.is_none() {
444+
// If the severity is not found, we should not report the diagnostic
445+
return None;
446+
}
447+
let oxc_diagnostic = oxc_diagnostic.with_severity(severity.map_or(
448+
Severity::Warning,
449+
|(severity, _)| {
450+
if severity == AllowWarnDeny::Deny {
451+
Severity::Error
452+
} else {
453+
Severity::Warning
454+
}
455+
},
456+
));
457+
458+
Some((path, oxc_diagnostic))
459+
})
460+
// group diagnostics by file path
461+
.fold(
462+
FxHashMap::default(),
463+
|mut acc: FxHashMap<PathBuf, Vec<OxcDiagnostic>>,
464+
(file_path, diagnostic)| {
465+
acc.entry(file_path).or_default().push(diagnostic);
466+
467+
acc
468+
},
469+
)
470+
.into_iter()
471+
.map(|(file_path, diagnostics)| {
472+
(
473+
file_path.clone(),
474+
DiagnosticService::wrap_diagnostics(
475+
tsgolint_state.cwd.clone(),
476+
file_path.clone(),
477+
&read_to_string(&file_path)
478+
.unwrap_or_else(|_| String::new()),
479+
0,
480+
diagnostics,
481+
),
482+
)
483+
})
484+
.collect::<FxHashMap<_, _>>();
485+
486+
for (file_path, diagnostics) in oxc_diagnostics {
487+
tsgolint_error_sender
488+
.send((file_path, diagnostics))
489+
.expect("Failed to send diagnostic");
490+
}
491+
492+
Ok(())
493+
}
494+
495+
Err(err) => Err(format!("Failed to parse tsgolint output: {err}")),
496+
}
497+
});
498+
499+
match handler.join() {
500+
Ok(Ok(())) => {
501+
// Successfully ran tsgolint
502+
}
503+
Ok(Err(err)) => {
504+
print_and_flush_stdout(stdout, &format!("Error running tsgolint: {err:?}"));
505+
return CliRunResult::TsGoLintError;
506+
}
507+
Err(err) => {
508+
print_and_flush_stdout(stdout, &format!("Error running tsgolint: {err:?}"));
509+
return CliRunResult::TsGoLintError;
510+
}
511+
}
512+
}
513+
323514
// Spawn linting in another thread so diagnostics can be printed immediately from diagnostic_service.run.
324515
rayon::spawn(move || {
325516
let mut lint_service = LintService::new(linter, allocator_pool, options);
@@ -1195,4 +1386,10 @@ mod test {
11951386
let args = &["-c", ".oxlintrc.json"];
11961387
Tester::new().with_cwd("fixtures/issue_11644".into()).test_and_snapshot(args);
11971388
}
1389+
1390+
#[test]
1391+
fn test_tsgolint() {
1392+
let args = &["fixtures/tsgolint", "--tsconfig", "fixtures/tsgolint/tsconfig.json"];
1393+
Tester::new().test_and_snapshot(args);
1394+
}
11981395
}

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
}

apps/oxlint/src/snapshots/_--ignore-path fixtures__linter__.customignore --no-ignore fixtures__linter__nan.js@oxlint.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ working directory:
1414
help: Use the isNaN function to compare with NaN.
1515
1616
Found 1 warning and 0 errors.
17-
Finished in <variable>ms on 1 file with 87 rules using 1 threads.
17+
Finished in <variable>ms on 1 file with 88 rules using 1 threads.
1818
----------
1919
CLI result: LintSucceeded
2020
----------

apps/oxlint/src/snapshots/_--ignore-pattern _____.js --ignore-pattern _____.vue fixtures__linter@oxlint.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ arguments: --ignore-pattern **/*.js --ignore-pattern **/*.vue fixtures/linter
66
working directory:
77
----------
88
Found 0 warnings and 0 errors.
9-
Finished in <variable>ms on 0 files with 87 rules using 1 threads.
9+
Finished in <variable>ms on 0 files with 88 rules using 1 threads.
1010
----------
1111
CLI result: LintSucceeded
1212
----------

0 commit comments

Comments
 (0)