Skip to content

Commit f8b936d

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 f8b936d

File tree

62 files changed

+729
-74
lines changed

Some content is hidden

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

62 files changed

+729
-74
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: 193 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, 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,47 @@ 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 type_aware = basic_options.tsconfig.is_some();
293+
283294
let report_unused_directives = match inline_config_options.report_unused_directives {
284295
ReportUnusedDirectives::WithoutSeverity(true) => Some(AllowWarnDeny::Warn),
285296
ReportUnusedDirectives::WithSeverity(Some(severity)) => Some(severity),
286297
_ => None,
287298
};
299+
let (mut diagnostic_service, tx_error) =
300+
Self::get_diagnostic_service(&output_formatter, &warning_options, &misc_options);
288301

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);
302+
let config_store = ConfigStore::new(lint_config, nested_configs, external_plugin_store);
303+
304+
let tsgolint_state = if type_aware {
305+
Some(TsGoLintState {
306+
error_sender: tx_error.clone(),
307+
config_store: config_store.clone(),
308+
executable_path: try_find_tsgolint_executable(options.cwd())
309+
.unwrap_or(PathBuf::from("tsgolint")),
310+
cwd: options.cwd().to_path_buf(),
311+
paths: paths.clone(),
312+
rules: config_store
313+
.rules()
314+
.iter()
315+
.filter_map(|(rule, status)| {
316+
if status.is_warn_deny() && rule.is_tsgolint_rule() {
317+
Some((*status, rule.clone()))
318+
} else {
319+
None
320+
}
321+
})
322+
.collect(),
323+
})
324+
} else {
325+
None
326+
};
327+
328+
let linter = Linter::new(LintOptions::default(), config_store, self.external_linter)
329+
.with_fix(fix_options.fix_kind())
330+
.with_report_unused_directives(report_unused_directives);
296331

297332
let tsconfig = basic_options.tsconfig;
298333
if let Some(path) = tsconfig.as_ref() {
@@ -313,13 +348,154 @@ impl Runner for LintRunner {
313348
}
314349
}
315350

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

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)