diff --git a/CHANGELOG.md b/CHANGELOG.md index 4364726e9c1..7978ef57ee4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,13 +14,15 @@ + import "module" with {} ``` - Fix an issue where JSON formatter does not respect `lineWidth` for arrays [#4351](https://github.com/rome/tools/issues/4351) + ### Linter #### Other changes - Code actions are formatted using Rome's formatter. If the formatter is disabled, the code action is not formatted. -- Fixed an issue that [`useShorthandArrayType`](https://docs.rome.tools/lint/rules/useShorthandArrayType) rule did not handle nested ReadonlyArray types correctly and erroneously reported TsObjectType [#4354](https://github.com/rome/tools/issues/4353) +- Fixed an issue that [`useShorthandArrayType`](https://docs.rome.tools/lint/rules/useShorthandArrayType) rule did not handle nested ReadonlyArray types correctly and erroneously reported TsObjectType [#4354](https://github.com/rome/tools/issues/4353). +- [`noUndeclaredVariables`](https://docs.rome.tools/lint/rules/noUndeclaredVariables) detects globals based on the file type. - Fix an issue when `noUndeclaredVariables` incorrectly identifies `AggregateError` as an undeclared variable. [#4365](https://github.com/rome/tools/issues/4365) #### New rules diff --git a/crates/rome_analyze/src/context.rs b/crates/rome_analyze/src/context.rs index ca02be01b7d..800715b0dba 100644 --- a/crates/rome_analyze/src/context.rs +++ b/crates/rome_analyze/src/context.rs @@ -1,6 +1,7 @@ use crate::{registry::RuleRoot, FromServices, Queryable, Rule, RuleKey, ServiceBag}; use rome_diagnostics::{Error, Result}; use std::ops::Deref; +use std::path::Path; type RuleQueryResult = <::Query as Queryable>::Output; type RuleServiceBag = <::Query as Queryable>::Services; @@ -17,6 +18,7 @@ where bag: &'a ServiceBag, services: RuleServiceBag, globals: &'a [&'a str], + file_path: &'a Path, } impl<'a, R> RuleContext<'a, R> @@ -28,6 +30,7 @@ where root: &'a RuleRoot, services: &'a ServiceBag, globals: &'a [&'a str], + file_path: &'a Path, ) -> Result { let rule_key = RuleKey::rule::(); Ok(Self { @@ -36,6 +39,7 @@ where bag: services, services: FromServices::from_services(&rule_key, services)?, globals, + file_path, }) } @@ -97,6 +101,18 @@ where pub fn is_global(&self, text: &str) -> bool { self.globals.contains(&text) } + + /// Returns the source type of the current file + pub fn source_type(&self) -> &T { + self.bag + .get_service::() + .expect("Source type is not registered") + } + + /// The file path of the current file + pub fn file_path(&self) -> &Path { + self.file_path + } } impl<'a, R> Deref for RuleContext<'a, R> diff --git a/crates/rome_analyze/src/lib.rs b/crates/rome_analyze/src/lib.rs index f336cb4896d..4f29eeed0da 100644 --- a/crates/rome_analyze/src/lib.rs +++ b/crates/rome_analyze/src/lib.rs @@ -5,6 +5,7 @@ use std::cmp::Ordering; use std::collections::{BTreeMap, BinaryHeap}; use std::fmt::{Debug, Display, Formatter}; use std::ops; +use std::path::Path; mod categories; pub mod context; @@ -77,6 +78,7 @@ pub struct AnalyzerContext<'a, L: Language> { pub services: ServiceBag, pub range: Option, pub globals: &'a [&'a str], + pub file_path: &'a Path, } impl<'analyzer, L, Matcher, Break, Diag> Analyzer<'analyzer, L, Matcher, Break, Diag> @@ -142,6 +144,7 @@ where range: ctx.range, globals: ctx.globals, apply_suppression_comment, + file_path: ctx.file_path, }; // The first phase being run will inspect the tokens and parse the @@ -220,6 +223,8 @@ struct PhaseRunner<'analyzer, 'phase, L: Language, Matcher, Break, Diag> { range: Option, /// Options passed to the analyzer globals: &'phase [&'phase str], + /// The [Path] of the current file + file_path: &'phase Path, } /// Single entry for a suppression comment in the `line_suppressions` buffer @@ -279,6 +284,7 @@ where signal_queue: &mut self.signal_queue, apply_suppression_comment: self.apply_suppression_comment, globals: self.globals, + file_path: self.file_path, }; visitor.visit(&node_event, ctx); @@ -304,6 +310,7 @@ where signal_queue: &mut self.signal_queue, apply_suppression_comment: self.apply_suppression_comment, globals: self.globals, + file_path: self.file_path, }; visitor.visit(&event, ctx); diff --git a/crates/rome_analyze/src/matcher.rs b/crates/rome_analyze/src/matcher.rs index 7edae7110ce..69f06dcabd4 100644 --- a/crates/rome_analyze/src/matcher.rs +++ b/crates/rome_analyze/src/matcher.rs @@ -3,6 +3,7 @@ use crate::{ SuppressionCommentEmitter, }; use rome_rowan::{Language, TextRange}; +use std::path::Path; use std::{ any::{Any, TypeId}, cmp::Ordering, @@ -27,6 +28,7 @@ pub struct MatchQueryParams<'phase, 'query, L: Language> { pub signal_queue: &'query mut BinaryHeap>, pub apply_suppression_comment: SuppressionCommentEmitter, pub globals: &'phase [&'phase str], + pub file_path: &'phase Path, } /// Wrapper type for a [QueryMatch] @@ -197,6 +199,7 @@ where #[cfg(test)] mod tests { use std::convert::Infallible; + use std::path::Path; use rome_diagnostics::{category, DiagnosticExt}; use rome_diagnostics::{Diagnostic, Severity}; @@ -381,6 +384,7 @@ mod tests { range: None, services: ServiceBag::default(), globals: &[], + file_path: Path::new(""), }; let result: Option = analyzer.run(ctx); diff --git a/crates/rome_analyze/src/options.rs b/crates/rome_analyze/src/options.rs index 31035754ebb..3f897c12f24 100644 --- a/crates/rome_analyze/src/options.rs +++ b/crates/rome_analyze/src/options.rs @@ -1,9 +1,11 @@ use crate::RuleKey; use serde::Deserialize; use std::collections::HashMap; +use std::fmt::Debug; +use std::path::PathBuf; /// A convenient new type data structure to store the options that belong to a rule -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Default, Deserialize)] pub struct RuleOptions(String); impl RuleOptions { @@ -19,7 +21,7 @@ impl RuleOptions { } /// A convenient new type data structure to insert and get rules -#[derive(Debug, Clone, Default)] +#[derive(Debug, Default)] pub struct AnalyzerRules(HashMap); impl AnalyzerRules { @@ -35,7 +37,7 @@ impl AnalyzerRules { } /// A data structured derived from the `rome.json` file -#[derive(Debug, Clone, Default)] +#[derive(Debug, Default)] pub struct AnalyzerConfiguration { /// A list of rules and their options pub rules: AnalyzerRules, @@ -47,8 +49,11 @@ pub struct AnalyzerConfiguration { } /// A set of information useful to the analyzer infrastructure -#[derive(Debug, Clone, Default)] +#[derive(Debug, Default)] pub struct AnalyzerOptions { /// A data structured derived from the [`rome.json`] file pub configuration: AnalyzerConfiguration, + + /// The file that is being analyzed + pub file_path: PathBuf, } diff --git a/crates/rome_analyze/src/registry.rs b/crates/rome_analyze/src/registry.rs index c445be1de65..4479410844a 100644 --- a/crates/rome_analyze/src/registry.rs +++ b/crates/rome_analyze/src/registry.rs @@ -426,12 +426,16 @@ impl RegistryRule { // if the query doesn't match let query_result = params.query.downcast_ref().unwrap(); let query_result = ::unwrap_match(params.services, query_result); - let ctx = - match RuleContext::new(&query_result, params.root, params.services, params.globals) - { - Ok(ctx) => ctx, - Err(error) => return Err(error), - }; + let ctx = match RuleContext::new( + &query_result, + params.root, + params.services, + params.globals, + params.file_path, + ) { + Ok(ctx) => ctx, + Err(error) => return Err(error), + }; for result in R::run(&ctx) { let text_range = @@ -446,6 +450,7 @@ impl RegistryRule { params.services, params.apply_suppression_comment, params.globals, + params.file_path, )); params.signal_queue.push(SignalEntry { diff --git a/crates/rome_analyze/src/signals.rs b/crates/rome_analyze/src/signals.rs index cce1458d853..ad322826f37 100644 --- a/crates/rome_analyze/src/signals.rs +++ b/crates/rome_analyze/src/signals.rs @@ -12,6 +12,7 @@ use rome_rowan::{BatchMutation, Language}; use std::borrow::Cow; use std::iter::FusedIterator; use std::marker::PhantomData; +use std::path::Path; use std::vec::IntoIter; /// Event raised by the analyzer when a [Rule](crate::Rule) @@ -245,6 +246,7 @@ pub(crate) struct RuleSignal<'phase, R: Rule> { apply_suppression_comment: SuppressionCommentEmitter>, /// A list of strings that are considered "globals" inside the analyzer globals: &'phase [&'phase str], + file_path: &'phase Path, } impl<'phase, R> RuleSignal<'phase, R> @@ -260,6 +262,7 @@ where <::Query as Queryable>::Language, >, globals: &'phase [&'phase str], + file_path: &'phase Path, ) -> Self { Self { root, @@ -268,6 +271,7 @@ where services, apply_suppression_comment, globals, + file_path, } } } @@ -277,14 +281,27 @@ where R: Rule + 'static, { fn diagnostic(&self) -> Option { - let ctx = - RuleContext::new(&self.query_result, self.root, self.services, self.globals).ok()?; + let ctx = RuleContext::new( + &self.query_result, + self.root, + self.services, + self.globals, + self.file_path, + ) + .ok()?; R::diagnostic(&ctx, &self.state).map(AnalyzerDiagnostic::from) } fn actions(&self) -> AnalyzerActionIter> { - let ctx = RuleContext::new(&self.query_result, self.root, self.services, self.globals).ok(); + let ctx = RuleContext::new( + &self.query_result, + self.root, + self.services, + self.globals, + self.file_path, + ) + .ok(); if let Some(ctx) = ctx { let mut actions = Vec::new(); if let Some(action) = R::action(&ctx, &self.state) { diff --git a/crates/rome_analyze/src/syntax.rs b/crates/rome_analyze/src/syntax.rs index 3d79325b9e7..d8abcad08e1 100644 --- a/crates/rome_analyze/src/syntax.rs +++ b/crates/rome_analyze/src/syntax.rs @@ -98,6 +98,7 @@ mod tests { AstNode, SyntaxNode, }; use std::convert::Infallible; + use std::path::Path; use crate::{ matcher::MatchQueryParams, registry::Phases, Analyzer, AnalyzerContext, AnalyzerSignal, @@ -165,6 +166,7 @@ mod tests { range: None, services: ServiceBag::default(), globals: &[], + file_path: Path::new(""), }; let result: Option = analyzer.run(ctx); diff --git a/crates/rome_analyze/src/visitor.rs b/crates/rome_analyze/src/visitor.rs index c1d6d200c4a..e08b2bd7e76 100644 --- a/crates/rome_analyze/src/visitor.rs +++ b/crates/rome_analyze/src/visitor.rs @@ -1,4 +1,5 @@ use std::collections::BinaryHeap; +use std::path::Path; use rome_rowan::{AstNode, Language, SyntaxNode, TextRange, WalkEvent}; @@ -18,6 +19,7 @@ pub struct VisitorContext<'phase, 'query, L: Language> { pub(crate) signal_queue: &'query mut BinaryHeap>, pub apply_suppression_comment: SuppressionCommentEmitter, pub globals: &'phase [&'phase str], + pub file_path: &'phase Path, } impl<'phase, 'query, L: Language> VisitorContext<'phase, 'query, L> { @@ -30,6 +32,7 @@ impl<'phase, 'query, L: Language> VisitorContext<'phase, 'query, L> { signal_queue: self.signal_queue, apply_suppression_comment: self.apply_suppression_comment, globals: self.globals, + file_path: self.file_path, }) } } diff --git a/crates/rome_js_analyze/src/lib.rs b/crates/rome_js_analyze/src/lib.rs index bb7f64cae01..06bf69e3de4 100644 --- a/crates/rome_js_analyze/src/lib.rs +++ b/crates/rome_js_analyze/src/lib.rs @@ -7,7 +7,7 @@ use rome_analyze::{ use rome_aria::{AriaProperties, AriaRoles}; use rome_diagnostics::{category, Diagnostic, Error as DiagnosticError}; use rome_js_syntax::suppression::SuppressionDiagnostic; -use rome_js_syntax::{suppression::parse_suppression_comment, JsLanguage}; +use rome_js_syntax::{suppression::parse_suppression_comment, JsLanguage, SourceType}; use serde::{Deserialize, Serialize}; use std::sync::Arc; use std::{borrow::Cow, error::Error}; @@ -56,6 +56,7 @@ pub fn analyze_with_inspect_matcher<'a, V, F, B>( filter: AnalysisFilter, inspect_matcher: V, options: &'a AnalyzerOptions, + source_type: SourceType, mut emit_signal: F, ) -> (Option, Vec) where @@ -120,6 +121,7 @@ where services.insert_service(Arc::new(AriaRoles::default())); services.insert_service(Arc::new(AriaProperties::default())); + services.insert_service(source_type); let globals: Vec<_> = options .configuration .globals @@ -132,6 +134,7 @@ where range: filter.range, services, globals: globals.as_slice(), + file_path: options.file_path.as_path(), }), diagnostics, ) @@ -144,13 +147,14 @@ pub fn analyze<'a, F, B>( root: &LanguageRoot, filter: AnalysisFilter, options: &'a AnalyzerOptions, + source_type: SourceType, emit_signal: F, ) -> (Option, Vec) where F: FnMut(&dyn AnalyzerSignal) -> ControlFlow + 'a, B: 'a, { - analyze_with_inspect_matcher(root, filter, |_| {}, options, emit_signal) + analyze_with_inspect_matcher(root, filter, |_| {}, options, source_type, emit_signal) } #[cfg(test)] @@ -193,6 +197,7 @@ mod tests { ..AnalysisFilter::default() }, &options, + SourceType::tsx(), |signal| { if let Some(diag) = signal.diagnostic() { error_ranges.push(diag.location().span.unwrap()); @@ -278,6 +283,7 @@ mod tests { &parsed.tree(), AnalysisFilter::default(), &options, + SourceType::js_module(), |signal| { if let Some(diag) = signal.diagnostic() { let span = diag.get_span(); @@ -356,16 +362,22 @@ mod tests { }; let options = AnalyzerOptions::default(); - analyze(&parsed.tree(), filter, &options, |signal| { - if let Some(diag) = signal.diagnostic() { - let code = diag.category().unwrap(); - if code != category!("suppressions/unused") { - panic!("unexpected diagnostic {code:?}"); + analyze( + &parsed.tree(), + filter, + &options, + SourceType::js_module(), + |signal| { + if let Some(diag) = signal.diagnostic() { + let code = diag.category().unwrap(); + if code != category!("suppressions/unused") { + panic!("unexpected diagnostic {code:?}"); + } } - } - ControlFlow::::Continue(()) - }); + ControlFlow::::Continue(()) + }, + ); } } diff --git a/crates/rome_js_analyze/src/semantic_analyzers/correctness/no_undeclared_variables.rs b/crates/rome_js_analyze/src/semantic_analyzers/correctness/no_undeclared_variables.rs index 040197ef1af..71ca74428a2 100644 --- a/crates/rome_js_analyze/src/semantic_analyzers/correctness/no_undeclared_variables.rs +++ b/crates/rome_js_analyze/src/semantic_analyzers/correctness/no_undeclared_variables.rs @@ -1,12 +1,12 @@ use crate::globals::browser::BROWSER; use crate::globals::node::NODE; -use crate::globals::runtime::ES_2021; +use crate::globals::runtime::{BUILTIN, ES_2021}; use crate::globals::typescript::TYPESCRIPT_BUILTIN; use crate::semantic_services::SemanticServices; use rome_analyze::context::RuleContext; use rome_analyze::{declare_rule, Rule, RuleDiagnostic}; use rome_console::markup; -use rome_js_syntax::{TextRange, TsAsExpression, TsReferenceType}; +use rome_js_syntax::{Language, SourceType, TextRange, TsAsExpression, TsReferenceType}; use rome_rowan::AstNode; declare_rule! { @@ -19,6 +19,16 @@ declare_rule! { /// ```js,expect_diagnostic /// foobar; /// ``` + /// + /// ```js,expect_diagnostic + /// // throw diagnostic for JavaScript files + /// PromiseLike; + /// ``` + /// ### Valid + /// + /// ```ts + /// type B = PromiseLike + /// ``` pub(crate) NoUndeclaredVariables { version: "0.10.0", name: "noUndeclaredVariables", @@ -45,6 +55,8 @@ impl Rule for NoUndeclaredVariables { let token = identifier.value_token().ok()?; let text = token.text_trimmed(); + let source_type = ctx.source_type::(); + if ctx.is_global(text) { return None; } @@ -54,7 +66,7 @@ impl Rule for NoUndeclaredVariables { return None; } - if is_global(text) { + if is_global(text, source_type) { return None; } @@ -76,9 +88,15 @@ impl Rule for NoUndeclaredVariables { } } -fn is_global(reference_name: &str) -> bool { +fn is_global(reference_name: &str, source_type: &SourceType) -> bool { ES_2021.binary_search(&reference_name).is_ok() || BROWSER.binary_search(&reference_name).is_ok() || NODE.binary_search(&reference_name).is_ok() - || TYPESCRIPT_BUILTIN.binary_search(&reference_name).is_ok() + || match source_type.language() { + Language::JavaScript => BUILTIN.binary_search(&reference_name).is_ok(), + Language::TypeScript { .. } => { + BUILTIN.binary_search(&reference_name).is_ok() + || TYPESCRIPT_BUILTIN.binary_search(&reference_name).is_ok() + } + } } diff --git a/crates/rome_js_analyze/tests/spec_tests.rs b/crates/rome_js_analyze/tests/spec_tests.rs index d6572ec5731..8a15a110fa7 100644 --- a/crates/rome_js_analyze/tests/spec_tests.rs +++ b/crates/rome_js_analyze/tests/spec_tests.rs @@ -148,7 +148,7 @@ pub(crate) fn write_analysis_to_snapshot( None }; - let (_, errors) = rome_js_analyze::analyze(&root, filter, &options, |event| { + let (_, errors) = rome_js_analyze::analyze(&root, filter, &options, source_type, |event| { if let Some(mut diag) = event.diagnostic() { for action in event.actions() { if check_action_type.is_suppression() { diff --git a/crates/rome_js_analyze/tests/specs/correctness/noUndeclaredVariables/invalidTsInJs.js b/crates/rome_js_analyze/tests/specs/correctness/noUndeclaredVariables/invalidTsInJs.js new file mode 100644 index 00000000000..f0a923ee68d --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/correctness/noUndeclaredVariables/invalidTsInJs.js @@ -0,0 +1,2 @@ +ArrayLike; +PromiseLike; diff --git a/crates/rome_js_analyze/tests/specs/correctness/noUndeclaredVariables/invalidTsInJs.js.snap b/crates/rome_js_analyze/tests/specs/correctness/noUndeclaredVariables/invalidTsInJs.js.snap new file mode 100644 index 00000000000..f7a42d5ec1d --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/correctness/noUndeclaredVariables/invalidTsInJs.js.snap @@ -0,0 +1,39 @@ +--- +source: crates/rome_js_analyze/tests/spec_tests.rs +expression: invalidTsInJs.js +--- +# Input +```js +ArrayLike; +PromiseLike; + +``` + +# Diagnostics +``` +invalidTsInJs.js:1:1 lint/correctness/noUndeclaredVariables ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! The ArrayLike variable is undeclared + + > 1 │ ArrayLike; + │ ^^^^^^^^^ + 2 │ PromiseLike; + 3 │ + + +``` + +``` +invalidTsInJs.js:2:1 lint/correctness/noUndeclaredVariables ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! The PromiseLike variable is undeclared + + 1 │ ArrayLike; + > 2 │ PromiseLike; + │ ^^^^^^^^^^^ + 3 │ + + +``` + + diff --git a/crates/rome_js_analyze/tests/specs/correctness/noUndeclaredVariables/validTsGlobals.ts b/crates/rome_js_analyze/tests/specs/correctness/noUndeclaredVariables/validTsGlobals.ts new file mode 100644 index 00000000000..12f39188ac0 --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/correctness/noUndeclaredVariables/validTsGlobals.ts @@ -0,0 +1 @@ +type B = PromiseLike; diff --git a/crates/rome_js_analyze/tests/specs/correctness/noUndeclaredVariables/validTsGlobals.ts.snap b/crates/rome_js_analyze/tests/specs/correctness/noUndeclaredVariables/validTsGlobals.ts.snap new file mode 100644 index 00000000000..eeb55cf9604 --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/correctness/noUndeclaredVariables/validTsGlobals.ts.snap @@ -0,0 +1,11 @@ +--- +source: crates/rome_js_analyze/tests/spec_tests.rs +expression: validTsGlobals.ts +--- +# Input +```js +type B = PromiseLike; + +``` + + diff --git a/crates/rome_service/src/file_handlers/javascript.rs b/crates/rome_service/src/file_handlers/javascript.rs index 4794426d71b..8d03c3a815d 100644 --- a/crates/rome_service/src/file_handlers/javascript.rs +++ b/crates/rome_service/src/file_handlers/javascript.rs @@ -37,6 +37,7 @@ use rome_parser::AnyParse; use rome_rowan::{AstNode, BatchMutationExt, Direction, NodeCache}; use std::borrow::Cow; use std::fmt::Debug; +use std::path::PathBuf; use tracing::debug; #[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] @@ -186,6 +187,7 @@ fn debug_control_flow(parse: AnyParse, cursor: TextSize) -> String { } }, &options, + SourceType::default(), |_| ControlFlow::::Continue(()), ); @@ -210,7 +212,8 @@ fn lint(params: LintParams) -> LintResults { let tree = params.parse.tree(); let mut diagnostics = params.parse.into_diagnostics(); - let analyzer_options = compute_analyzer_options(¶ms.settings); + let analyzer_options = + compute_analyzer_options(¶ms.settings, PathBuf::from(params.path.as_path())); let mut diagnostic_count = diagnostics.len() as u64; let mut errors = diagnostics @@ -220,47 +223,53 @@ fn lint(params: LintParams) -> LintResults { let has_lint = params.filter.categories.contains(RuleCategories::LINT); - let (_, analyze_diagnostics) = analyze(&tree, params.filter, &analyzer_options, |signal| { - if let Some(mut diagnostic) = signal.diagnostic() { - // Do not report unused suppression comment diagnostics if this is a syntax-only analyzer pass - if !has_lint && diagnostic.category() == Some(category!("suppressions/unused")) { - return ControlFlow::::Continue(()); - } + let (_, analyze_diagnostics) = analyze( + &tree, + params.filter, + &analyzer_options, + SourceType::default(), + |signal| { + if let Some(mut diagnostic) = signal.diagnostic() { + // Do not report unused suppression comment diagnostics if this is a syntax-only analyzer pass + if !has_lint && diagnostic.category() == Some(category!("suppressions/unused")) { + return ControlFlow::::Continue(()); + } - diagnostic_count += 1; - - // We do now check if the severity of the diagnostics should be changed. - // The configuration allows to change the severity of the diagnostics emitted by rules. - let severity = diagnostic - .category() - .filter(|category| category.name().starts_with("lint/")) - .map(|category| { - params - .rules - .and_then(|rules| rules.get_severity_from_code(category)) - .unwrap_or(Severity::Warning) - }) - .unwrap_or_else(|| diagnostic.severity()); - - if severity <= Severity::Error { - errors += 1; - } + diagnostic_count += 1; + + // We do now check if the severity of the diagnostics should be changed. + // The configuration allows to change the severity of the diagnostics emitted by rules. + let severity = diagnostic + .category() + .filter(|category| category.name().starts_with("lint/")) + .map(|category| { + params + .rules + .and_then(|rules| rules.get_severity_from_code(category)) + .unwrap_or(Severity::Warning) + }) + .unwrap_or_else(|| diagnostic.severity()); + + if severity <= Severity::Error { + errors += 1; + } - if diagnostic_count <= params.max_diagnostics { - for action in signal.actions() { - if !action.is_suppression() { - diagnostic = diagnostic.add_code_suggestion(action.into()); + if diagnostic_count <= params.max_diagnostics { + for action in signal.actions() { + if !action.is_suppression() { + diagnostic = diagnostic.add_code_suggestion(action.into()); + } } - } - let error = diagnostic.with_severity(severity); + let error = diagnostic.with_severity(severity); - diagnostics.push(rome_diagnostics::serde::Diagnostic::new(error)); + diagnostics.push(rome_diagnostics::serde::Diagnostic::new(error)); + } } - } - ControlFlow::::Continue(()) - }); + ControlFlow::::Continue(()) + }, + ); diagnostics.extend( analyze_diagnostics @@ -310,6 +319,7 @@ fn code_actions( range: TextRange, rules: Option<&Rules>, settings: SettingsHandle, + path: &RomePath, ) -> PullActionsResult { let tree = parse.tree(); @@ -336,21 +346,27 @@ fn code_actions( filter.categories = RuleCategories::default(); filter.range = Some(range); - let analyzer_options = compute_analyzer_options(&settings); + let analyzer_options = compute_analyzer_options(&settings, PathBuf::from(path.as_path())); - analyze(&tree, filter, &analyzer_options, |signal| { - actions.extend(signal.actions().into_code_action_iter().map(|item| { - CodeAction { - category: item.category.clone(), - rule_name: item - .rule_name - .map(|(group, name)| (Cow::Borrowed(group), Cow::Borrowed(name))), - suggestion: item.suggestion, - } - })); + analyze( + &tree, + filter, + &analyzer_options, + SourceType::default(), + |signal| { + actions.extend(signal.actions().into_code_action_iter().map(|item| { + CodeAction { + category: item.category.clone(), + rule_name: item + .rule_name + .map(|(group, name)| (Cow::Borrowed(group), Cow::Borrowed(name))), + suggestion: item.suggestion, + } + })); - ControlFlow::::Continue(()) - }); + ControlFlow::::Continue(()) + }, + ); PullActionsResult { actions } } @@ -387,47 +403,53 @@ fn fix_all(params: FixAllParams) -> Result { let mut skipped_suggested_fixes = 0; let mut errors: u16 = 0; - let analyzer_options = compute_analyzer_options(&settings); + let analyzer_options = compute_analyzer_options(&settings, PathBuf::from(rome_path.as_path())); loop { - let (action, _) = analyze(&tree, filter, &analyzer_options, |signal| { - let current_diagnostic = signal.diagnostic(); - - if let Some(diagnostic) = current_diagnostic.as_ref() { - if is_diagnostic_error(diagnostic, params.rules) { - errors += 1; + let (action, _) = analyze( + &tree, + filter, + &analyzer_options, + SourceType::default(), + |signal| { + let current_diagnostic = signal.diagnostic(); + + if let Some(diagnostic) = current_diagnostic.as_ref() { + if is_diagnostic_error(diagnostic, params.rules) { + errors += 1; + } } - } - for action in signal.actions() { - // suppression actions should not be part of the fixes (safe or suggested) - if action.is_suppression() { - continue; - } + for action in signal.actions() { + // suppression actions should not be part of the fixes (safe or suggested) + if action.is_suppression() { + continue; + } - match fix_file_mode { - FixFileMode::SafeFixes => { - if action.applicability == Applicability::MaybeIncorrect { - skipped_suggested_fixes += 1; - } - if action.applicability == Applicability::Always { - errors = errors.saturating_sub(1); - return ControlFlow::Break(action); + match fix_file_mode { + FixFileMode::SafeFixes => { + if action.applicability == Applicability::MaybeIncorrect { + skipped_suggested_fixes += 1; + } + if action.applicability == Applicability::Always { + errors = errors.saturating_sub(1); + return ControlFlow::Break(action); + } } - } - FixFileMode::SafeAndUnsafeFixes => { - if matches!( - action.applicability, - Applicability::Always | Applicability::MaybeIncorrect - ) { - errors = errors.saturating_sub(1); - return ControlFlow::Break(action); + FixFileMode::SafeAndUnsafeFixes => { + if matches!( + action.applicability, + Applicability::Always | Applicability::MaybeIncorrect + ) { + errors = errors.saturating_sub(1); + return ControlFlow::Break(action); + } } } } - } - ControlFlow::Continue(()) - }); + ControlFlow::Continue(()) + }, + ); match action { Some(action) => { @@ -592,16 +614,22 @@ fn organize_imports(parse: AnyParse) -> Result Result AnalyzerOptions { +fn compute_analyzer_options(settings: &SettingsHandle, file_path: PathBuf) -> AnalyzerOptions { let configuration = to_analyzer_configuration( settings.as_ref().linter(), &settings.as_ref().languages, @@ -640,5 +668,8 @@ fn compute_analyzer_options(settings: &SettingsHandle) -> AnalyzerOptions { } }, ); - AnalyzerOptions { configuration } + AnalyzerOptions { + configuration, + file_path, + } } diff --git a/crates/rome_service/src/file_handlers/json.rs b/crates/rome_service/src/file_handlers/json.rs index c40b9ee836c..b642df78d0f 100644 --- a/crates/rome_service/src/file_handlers/json.rs +++ b/crates/rome_service/src/file_handlers/json.rs @@ -216,6 +216,7 @@ fn code_actions( _range: TextRange, _rules: Option<&Rules>, _settings: SettingsHandle, + _path: &RomePath, ) -> PullActionsResult { PullActionsResult { actions: Vec::new(), diff --git a/crates/rome_service/src/file_handlers/mod.rs b/crates/rome_service/src/file_handlers/mod.rs index 417ca8c617f..f91a998fa37 100644 --- a/crates/rome_service/src/file_handlers/mod.rs +++ b/crates/rome_service/src/file_handlers/mod.rs @@ -197,7 +197,8 @@ pub(crate) struct LintResults { } type Lint = fn(LintParams) -> LintResults; -type CodeActions = fn(AnyParse, TextRange, Option<&Rules>, SettingsHandle) -> PullActionsResult; +type CodeActions = + fn(AnyParse, TextRange, Option<&Rules>, SettingsHandle, &RomePath) -> PullActionsResult; type FixAll = fn(FixAllParams) -> Result; type Rename = fn(&RomePath, AnyParse, TextSize, String) -> Result; type OrganizeImports = fn(AnyParse) -> Result; diff --git a/crates/rome_service/src/workspace/server.rs b/crates/rome_service/src/workspace/server.rs index e049d75426c..c358067cad0 100644 --- a/crates/rome_service/src/workspace/server.rs +++ b/crates/rome_service/src/workspace/server.rs @@ -453,7 +453,13 @@ impl Workspace for WorkspaceServer { let parse = self.get_parse(params.path.clone(), Some(FeatureName::Lint))?; let settings = self.settings.read().unwrap(); let rules = settings.linter().rules.as_ref(); - Ok(code_actions(parse, params.range, rules, self.settings())) + Ok(code_actions( + parse, + params.range, + rules, + self.settings(), + ¶ms.path, + )) } /// Runs the given file through the formatter using the provided options diff --git a/website/src/pages/lint/rules/noUndeclaredVariables.md b/website/src/pages/lint/rules/noUndeclaredVariables.md index 102e7ce6d26..3b7e046639f 100644 --- a/website/src/pages/lint/rules/noUndeclaredVariables.md +++ b/website/src/pages/lint/rules/noUndeclaredVariables.md @@ -25,6 +25,28 @@ foobar; +```jsx +// throw diagnostic for JavaScript files +PromiseLike; +``` + +
correctness/noUndeclaredVariables.js:2:1 lint/correctness/noUndeclaredVariables ━━━━━━━━━━━━━━━━━━━━
+
+   The PromiseLike variable is undeclared
+  
+    1 │ // throw diagnostic for JavaScript files
+  > 2 │ PromiseLike;
+   ^^^^^^^^^^^
+    3 │ 
+  
+
+ +### Valid + +```ts +type B = PromiseLike +``` + ## Related links - [Disable a rule](/linter/#disable-a-lint-rule) diff --git a/xtask/bench/src/language.rs b/xtask/bench/src/language.rs index e3b63adcf29..2a68e478c97 100644 --- a/xtask/bench/src/language.rs +++ b/xtask/bench/src/language.rs @@ -123,7 +123,7 @@ impl Analyze { ..AnalysisFilter::default() }; let options = AnalyzerOptions::default(); - analyze(root, filter, &options, |event| { + analyze(root, filter, &options, SourceType::default(), |event| { black_box(event.diagnostic()); black_box(event.actions()); ControlFlow::::Continue(()) diff --git a/xtask/lintdoc/src/main.rs b/xtask/lintdoc/src/main.rs index 711fbac9862..98043295e5f 100644 --- a/xtask/lintdoc/src/main.rs +++ b/xtask/lintdoc/src/main.rs @@ -551,7 +551,7 @@ fn assert_lint( }; let options = AnalyzerOptions::default(); - let (_, diagnostics) = analyze(&root, filter, &options, |signal| { + let (_, diagnostics) = analyze(&root, filter, &options, source_type, |signal| { if let Some(mut diag) = signal.diagnostic() { let category = diag.category().expect("linter diagnostic has no code"); let severity = settings.get_severity_from_rule_code(category).expect(