diff --git a/crates/rome_js_analyze/tests/specs/correctness/noUndeclaredVariables.ts b/crates/rome_js_analyze/tests/specs/correctness/noUndeclaredVariables.ts new file mode 100644 index 00000000000..1062e1fe2e1 --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/correctness/noUndeclaredVariables.ts @@ -0,0 +1,12 @@ +// valid +export type EventHandler = `on${T}` +export type EventHandlerDefault = `on${T}` + +export type NestedContext> = '' | `(${S})` +export type NestedContextDefault = '' | `(${S})` + +export type Whatever = `Hello ${S}` +export type WhateverDefault = `Hello ${S}` + +// Invalid +export type Invalid = `Hello ${T}` \ No newline at end of file diff --git a/crates/rome_js_analyze/tests/specs/correctness/noUndeclaredVariables.ts.snap b/crates/rome_js_analyze/tests/specs/correctness/noUndeclaredVariables.ts.snap new file mode 100644 index 00000000000..9eb9a25b148 --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/correctness/noUndeclaredVariables.ts.snap @@ -0,0 +1,34 @@ +--- +source: crates/rome_js_analyze/tests/spec_tests.rs +expression: noUndeclaredVariables.ts +--- +# Input +```js +// valid +export type EventHandler = `on${T}` +export type EventHandlerDefault = `on${T}` + +export type NestedContext> = '' | `(${S})` +export type NestedContextDefault = '' | `(${S})` + +export type Whatever = `Hello ${S}` +export type WhateverDefault = `Hello ${S}` + +// Invalid +export type Invalid = `Hello ${T}` +``` + +# Diagnostics +``` +noUndeclaredVariables.ts:12:50 lint/correctness/noUndeclaredVariables ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! The T variable is undeclared + + 11 │ // Invalid + > 12 │ export type Invalid = `Hello ${T}` + │ ^ + + +``` + + diff --git a/crates/rome_js_semantic/src/events.rs b/crates/rome_js_semantic/src/events.rs index eeba4f29cb1..79603afdccb 100644 --- a/crates/rome_js_semantic/src/events.rs +++ b/crates/rome_js_semantic/src/events.rs @@ -8,7 +8,7 @@ use rome_js_syntax::{ JsCallExpression, JsForVariableDeclaration, JsIdentifierAssignment, JsIdentifierBinding, JsLanguage, JsParenthesizedExpression, JsReferenceIdentifier, JsSyntaxKind, JsSyntaxNode, JsSyntaxToken, JsVariableDeclaration, JsVariableDeclarator, JsVariableDeclaratorList, - JsxReferenceIdentifier, TextRange, TextSize, TsIdentifierBinding, + JsxReferenceIdentifier, TextRange, TextSize, TsIdentifierBinding, TsTypeParameter, }; use rome_rowan::{syntax::Preorder, AstNode, SyntaxNodeCast, SyntaxNodeOptionExt, SyntaxTokenText}; @@ -245,7 +245,7 @@ impl SemanticEventExtractor { use rome_js_syntax::JsSyntaxKind::*; match node.kind() { - JS_IDENTIFIER_BINDING | TS_IDENTIFIER_BINDING => { + JS_IDENTIFIER_BINDING | TS_IDENTIFIER_BINDING | TS_TYPE_PARAMETER => { self.enter_identifier_binding(node); } JS_REFERENCE_IDENTIFIER | JSX_REFERENCE_IDENTIFIER => { @@ -322,8 +322,8 @@ impl SemanticEventExtractor { use JsSyntaxKind::*; debug_assert!(matches!( node.kind(), - JS_IDENTIFIER_BINDING | TS_IDENTIFIER_BINDING - ), "specified node is not a identifier binding (JS_IDENTIFIER_BINDING, TS_IDENTIFIER_BINDING)"); + JS_IDENTIFIER_BINDING | TS_IDENTIFIER_BINDING | TS_TYPE_PARAMETER + ), "specified node is not a identifier binding (JS_IDENTIFIER_BINDING, TS_IDENTIFIER_BINDING, TS_TYPE_PARAMETER)"); let (name_token, is_var) = match node.kind() { JS_IDENTIFIER_BINDING => { @@ -338,6 +338,12 @@ impl SemanticEventExtractor { let is_var = Self::is_var(&binding); (name_token, is_var) } + TS_TYPE_PARAMETER => { + let binding = node.clone().cast::()?; + let name_token = binding.name().ok()?.ident_token().ok()?; + let is_var = Self::is_var(&binding); + (name_token, is_var) + } _ => return None, }; diff --git a/crates/rome_js_semantic/src/tests/assertions.rs b/crates/rome_js_semantic/src/tests/assertions.rs index 0d68f0c5186..e1a8e6969ad 100644 --- a/crates/rome_js_semantic/src/tests/assertions.rs +++ b/crates/rome_js_semantic/src/tests/assertions.rs @@ -669,10 +669,6 @@ impl SemanticAssertions { // where we expect let e = events.iter().find(|event| match event { SemanticEvent::ScopeEnded { started_at, .. } => { - println!( - "started_at: {:?} scope_start_assertions_range: {:?}", - started_at, scope_start_assertions_range - ); *started_at == scope_start_assertions_range.start() } _ => false, diff --git a/crates/rome_js_semantic/src/tests/scopes.rs b/crates/rome_js_semantic/src/tests/scopes.rs index bf92d522184..300e93305fc 100644 --- a/crates/rome_js_semantic/src/tests/scopes.rs +++ b/crates/rome_js_semantic/src/tests/scopes.rs @@ -32,6 +32,21 @@ assert_semantics! { ok_scope_class_setter, ";class A { set/*START A*/ name(v) {}/*END A*/ }", } +// Type parameters +assert_semantics! { + ok_type_parameter, "export type /*START A*/ EventHandler = `on${ Event /*READ Event */ }` /*END A*/", + ok_type_parameter_with_default, "export type /*START A */ EventHandler = `on${ Event /*READ Event */ }` /*END A*/", + ok_type_parameter_multiple_declaration, " + export type /*START ScopeA */ EventHandler = `on${ Event /*READ EventA */ }`; /*END ScopeA */ + export type /*START ScopeB */ EventHandlerDefault = `on${ Event /*READ EventB */ }`; /*END ScopeB */ + ", + ok_type_parameter_interface, " + export interface /*START A*/ EventHandler { + [`on${ Event /*READ Event */ }`]: (event: Event /*READ Event */, data: unknown) => void; + } /*END A*/ + ", +} + // Others assert_semantics! { ok_scope_global, "/*START GLOBAL*//*END GLOBAL*/",