Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(linter): typescript-eslint/no-floating-promises #2912

Closed
wants to merge 24 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
d7c00d1
no-floating-promises boilerplate
valeneiko Apr 7, 2024
9e8ba4d
Option for rules requiring type info
valeneiko Apr 7, 2024
01f156d
Copy typecheck server POC into oxc
valeneiko Apr 10, 2024
962ce3a
Add typecheck server scripts to justfile
valeneiko Apr 10, 2024
1d4839d
Construct type checker
valeneiko Apr 10, 2024
1c03338
Start implementing the rule
valeneiko Apr 10, 2024
70dba38
Update typecheck server to use Span instead of line+col
valeneiko Apr 14, 2024
15002e0
Add type checker to LintContext
valeneiko Apr 14, 2024
9586057
Deserialize Typecheck Server responses
valeneiko Apr 14, 2024
e467062
Add isValidRejectionHandler command to typecheck server
valeneiko Apr 14, 2024
6a3b125
Add get_span helper to some AST enums
valeneiko Apr 14, 2024
07c0b96
More lint rule implementation
valeneiko Apr 14, 2024
16a9833
Deserialize errors into owned string
valeneiko Apr 14, 2024
08427c4
Resolve full path before sending to typecheck server
valeneiko Apr 24, 2024
71b481f
Add responses to all commands
valeneiko Apr 24, 2024
09014ff
Use absolute paths for all typecheck calls
valeneiko Apr 25, 2024
2c15fb0
Implement result handler for no-floating-promises
valeneiko Apr 25, 2024
5ed0d3c
Finish implementing the rule
valeneiko Apr 26, 2024
3e570f2
Cache node resolution JS side
valeneiko Apr 26, 2024
18eb675
Resolve typecheck server path relative to oxlint executable
valeneiko Apr 26, 2024
2b11d74
Update typecheck server path to make tests runnable
valeneiko Apr 27, 2024
43ece84
Fix a few test failures
valeneiko Apr 27, 2024
fae30c7
Ignore errors
valeneiko Apr 27, 2024
600d6b8
Collect typecheck stats
valeneiko Apr 28, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions crates/oxc_ast/src/ast/js.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,51 @@ impl<'a> Expression<'a> {
_ => false,
}
}

pub fn get_span(&self) -> &Span {
match self {
Expression::BooleanLiteral(e) => &e.span,
Expression::NullLiteral(e) => &e.span,
Expression::NumericLiteral(e) => &e.span,
Expression::BigintLiteral(e) => &e.span,
Expression::RegExpLiteral(e) => &e.span,
Expression::StringLiteral(e) => &e.span,
Expression::TemplateLiteral(e) => &e.span,
Expression::Identifier(e) => &e.span,
Expression::MetaProperty(e) => &e.span,
Expression::Super(e) => &e.span,
Expression::ArrayExpression(e) => &e.span,
Expression::ArrowFunctionExpression(e) => &e.span,
Expression::AssignmentExpression(e) => &e.span,
Expression::AwaitExpression(e) => &e.span,
Expression::BinaryExpression(e) => &e.span,
Expression::CallExpression(e) => &e.span,
Expression::ChainExpression(e) => &e.span,
Expression::ClassExpression(e) => &e.span,
Expression::ConditionalExpression(e) => &e.span,
Expression::FunctionExpression(e) => &e.span,
Expression::ImportExpression(e) => &e.span,
Expression::LogicalExpression(e) => &e.span,
Expression::MemberExpression(e) => e.get_span(),
Expression::NewExpression(e) => &e.span,
Expression::ObjectExpression(e) => &e.span,
Expression::ParenthesizedExpression(e) => &e.span,
Expression::SequenceExpression(e) => &e.span,
Expression::TaggedTemplateExpression(e) => &e.span,
Expression::ThisExpression(e) => &e.span,
Expression::UnaryExpression(e) => &e.span,
Expression::UpdateExpression(e) => &e.span,
Expression::YieldExpression(e) => &e.span,
Expression::PrivateInExpression(e) => &e.span,
Expression::JSXElement(e) => &e.span,
Expression::JSXFragment(e) => &e.span,
Expression::TSAsExpression(e) => &e.span,
Expression::TSSatisfiesExpression(e) => &e.span,
Expression::TSTypeAssertion(e) => &e.span,
Expression::TSNonNullExpression(e) => &e.span,
Expression::TSInstantiationExpression(e) => &e.span,
}
}
}

/// Identifier Name
Expand Down Expand Up @@ -718,6 +763,14 @@ impl<'a> MemberExpression<'a> {
self.object().is_specific_id(object)
&& self.static_property_name().is_some_and(|p| p == property)
}

pub fn get_span(&self) -> &Span {
match self {
MemberExpression::ComputedMemberExpression(e) => &e.span,
MemberExpression::StaticMemberExpression(e) => &e.span,
MemberExpression::PrivateFieldExpression(e) => &e.span,
}
}
}

/// `MemberExpression[?Yield, ?Await] [ Expression[+In, ?Yield, ?Await] ]`
Expand Down Expand Up @@ -856,6 +909,13 @@ impl Argument<'_> {
pub fn is_spread(&self) -> bool {
matches!(self, Self::SpreadElement(_))
}

pub fn get_span(&self) -> &Span {
match self {
Argument::SpreadElement(e) => &e.span,
Argument::Expression(e) => e.get_span(),
}
}
}

/// Update Expression
Expand Down Expand Up @@ -1179,6 +1239,15 @@ pub enum ChainElement<'a> {
MemberExpression(Box<'a, MemberExpression<'a>>),
}

impl<'a> ChainElement<'a> {
pub fn get_span(&self) -> &Span {
match self {
ChainElement::CallExpression(e) => &e.span,
ChainElement::MemberExpression(e) => e.get_span(),
}
}
}

/// Parenthesized Expression
#[derive(Debug, Hash)]
#[cfg_attr(feature = "serialize", derive(Serialize, Tsify))]
Expand Down
4 changes: 4 additions & 0 deletions crates/oxc_cli/src/command/lint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,10 @@ pub struct EnablePlugins {
/// Enable the React performance plugin and detect rendering performance problems
#[bpaf(switch, hide_usage)]
pub react_perf_plugin: bool,

/// Enable the TypeCheck plugin and detect type-based problems
#[bpaf(switch, hide_usage)]
pub typecheck_plugin: bool,
}

#[cfg(test)]
Expand Down
3 changes: 2 additions & 1 deletion crates/oxc_cli/src/lint/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ impl Runner for LintRunner {
.with_jest_plugin(enable_plugins.jest_plugin)
.with_jsx_a11y_plugin(enable_plugins.jsx_a11y_plugin)
.with_nextjs_plugin(enable_plugins.nextjs_plugin)
.with_react_perf_plugin(enable_plugins.react_perf_plugin);
.with_react_perf_plugin(enable_plugins.react_perf_plugin)
.with_type_info(enable_plugins.typecheck_plugin);

let linter = match Linter::from_options(lint_options) {
Ok(lint_service) => lint_service,
Expand Down
3 changes: 3 additions & 0 deletions crates/oxc_language_server/src/linter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,8 +300,11 @@ impl IsolatedLintHandler {
};

let lint_ctx = LintContext::new(
Path::new("./"),
path.to_path_buf().into_boxed_path(),
&Rc::new(semantic_ret.semantic),
// TODO: create type checker
None,
);

let result = linter.run(lint_ctx);
Expand Down
Empty file.
18 changes: 18 additions & 0 deletions crates/oxc_linter/fixtures/typecheck/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"compilerOptions": {
"jsx": "preserve",
"target": "es5",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"lib": [
"es2015",
"es2017",
"esnext"
],
"experimentalDecorators": true
},
"include": [
"file.ts"
]
}
35 changes: 33 additions & 2 deletions crates/oxc_linter/src/context.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
use std::{cell::RefCell, path::Path, rc::Rc, sync::Arc};
use std::{
cell::RefCell,
path::Path,
rc::Rc,
sync::{Arc, Mutex},
};

use oxc_codegen::{Codegen, CodegenOptions};
use oxc_diagnostics::Error;
Expand All @@ -9,6 +14,7 @@ use crate::{
disable_directives::{DisableDirectives, DisableDirectivesBuilder},
fixer::{Fix, Message},
javascript_globals::GLOBALS,
typecheck::TSServerClient,
ESLintEnv, ESLintSettings,
};

Expand All @@ -26,24 +32,36 @@ pub struct LintContext<'a> {

file_path: Box<Path>,

absolute_path: Box<String>,

settings: Arc<ESLintSettings>,

env: Arc<ESLintEnv>,

type_checker: Option<Arc<Mutex<TSServerClient>>>,
}

impl<'a> LintContext<'a> {
pub fn new(file_path: Box<Path>, semantic: &Rc<Semantic<'a>>) -> Self {
pub fn new(
cwd: &Path,
file_path: Box<Path>,
semantic: &Rc<Semantic<'a>>,
type_checker: Option<Arc<Mutex<TSServerClient>>>,
) -> Self {
let disable_directives =
DisableDirectivesBuilder::new(semantic.source_text(), semantic.trivias()).build();
let absolute_path = Box::new(cwd.join(file_path.as_ref()).to_string_lossy().into());
Self {
semantic: Rc::clone(semantic),
diagnostics: RefCell::new(vec![]),
disable_directives,
fix: false,
current_rule_name: "",
file_path,
absolute_path,
settings: Arc::new(ESLintSettings::default()),
env: Arc::new(ESLintEnv::default()),
type_checker,
}
}

Expand Down Expand Up @@ -89,6 +107,10 @@ impl<'a> LintContext<'a> {
&self.file_path
}

pub fn absolute_path(&self) -> &str {
&self.absolute_path.as_ref()
}

pub fn envs(&self) -> &ESLintEnv {
&self.env
}
Expand All @@ -109,6 +131,15 @@ impl<'a> LintContext<'a> {
self.current_rule_name = name;
}

pub fn use_type_checker<F, R>(&self, run: F) -> R
where
F: FnOnce(&mut TSServerClient) -> R,
{
// Unwrap is safe here, since rules requiring type checker will not run unless type checker is created
let mut type_checker = self.type_checker.as_ref().unwrap().lock().unwrap();
run(&mut type_checker)
}

/* Diagnostics */

pub fn into_message(self) -> Vec<Message<'a>> {
Expand Down
1 change: 1 addition & 0 deletions crates/oxc_linter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub mod partial_loader;
pub mod rule;
mod rules;
mod service;
mod typecheck;
mod utils;

use rustc_hash::FxHashMap;
Expand Down
12 changes: 12 additions & 0 deletions crates/oxc_linter/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pub struct LintOptions {
pub jsx_a11y_plugin: bool,
pub nextjs_plugin: bool,
pub react_perf_plugin: bool,
pub type_info: bool,
pub env: ESLintEnv,
}

Expand All @@ -43,6 +44,7 @@ impl Default for LintOptions {
jsx_a11y_plugin: false,
nextjs_plugin: false,
react_perf_plugin: false,
type_info: false,
env: ESLintEnv::default(),
}
}
Expand Down Expand Up @@ -105,6 +107,12 @@ impl LintOptions {
self
}

#[must_use]
pub fn with_type_info(mut self, yes: bool) -> Self {
self.type_info = yes;
self
}

#[must_use]
pub fn with_env(mut self, env: Vec<String>) -> Self {
self.env = ESLintEnv::from_vec(env);
Expand Down Expand Up @@ -251,6 +259,10 @@ impl LintOptions {
may_exclude_plugin_rules(self.nextjs_plugin, NEXTJS_PLUGIN_NAME);
may_exclude_plugin_rules(self.react_perf_plugin, REACT_PERF_PLUGIN_NAME);

if !self.type_info {
rules.retain(|rule| !rule.requires_type_info())
}

rules
}
}
1 change: 1 addition & 0 deletions crates/oxc_linter/src/rule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub trait RuleMeta {
const NAME: &'static str;

const CATEGORY: RuleCategory;
const REQUIRES_TYPE_INFO: bool;

fn documentation() -> Option<&'static str> {
None
Expand Down
2 changes: 2 additions & 0 deletions crates/oxc_linter/src/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ mod typescript {
pub mod no_empty_interface;
pub mod no_explicit_any;
pub mod no_extra_non_null_assertion;
pub mod no_floating_promises;
pub mod no_misused_new;
pub mod no_namespace;
pub mod no_non_null_asserted_optional_chain;
Expand Down Expand Up @@ -462,6 +463,7 @@ oxc_macros::declare_all_lint_rules! {
typescript::no_empty_interface,
typescript::no_explicit_any,
typescript::no_extra_non_null_assertion,
typescript::no_floating_promises,
typescript::no_misused_new,
typescript::no_namespace,
typescript::no_non_null_asserted_optional_chain,
Expand Down
Loading