chore: add flags to regex literals (#8279)
Some checks are pending
Benchmarks JS / Bench (push) Waiting to run
CI on main / Test (push) Waiting to run
CI on main / Format Rust Files (push) Waiting to run
CI on main / Lint Rust Files (push) Waiting to run
CI on main / Check Dependencies (push) Waiting to run
CI on main / Test262 Coverage (push) Waiting to run
Release / Release (push) Waiting to run
Release / version (push) Blocked by required conditions
Release / Package darwin-arm64 (push) Blocked by required conditions
Release / Package darwin-x64 (push) Blocked by required conditions
Release / Package linux-arm64-musl (push) Blocked by required conditions
Release / Package linux-x64-musl (push) Blocked by required conditions
Release / Package win32-arm64 (push) Blocked by required conditions
Release / Package win32-x64 (push) Blocked by required conditions
Release / Package linux-arm64 (push) Blocked by required conditions
Release / Package linux-x64 (push) Blocked by required conditions
Release / Build WASM (push) Blocked by required conditions
Release / Package JavaScript APIs (push) Blocked by required conditions
Release / Publish CLI (push) Blocked by required conditions
Release / Publish JS API (push) Blocked by required conditions
Repository dispatch on main / Build @biomejs/wasm-web (push) Waiting to run
Repository dispatch on main / Repository dispatch (push) Blocked by required conditions

This commit is contained in:
Arend van Beelen jr. 2025-11-26 19:49:43 +01:00 committed by GitHub
parent ac0fb2d1c5
commit 06f99f45f5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 91 additions and 5 deletions

View file

@ -818,7 +818,14 @@ impl Format<FormatTypeContext> for Literal {
Self::Object(obj) => write!(f, [&obj]),
Self::RegExp(regex) => write!(
f,
[token("regex:"), space(), text(regex, TextSize::default())]
[
token("regex:"),
space(),
token("/"),
text(&regex.pattern, TextSize::default()),
token("/"),
text(&regex.flags, TextSize::default())
]
),
Self::String(lit) => write!(
f,

View file

@ -22,13 +22,13 @@ use biome_js_syntax::{
TsReturnTypeAnnotation, TsTypeAliasDeclaration, TsTypeAnnotation, TsTypeArguments, TsTypeList,
TsTypeParameter, TsTypeParameters, TsTypeofType, inner_string_text, unescape_js_string,
};
use biome_rowan::{AstNode, SyntaxResult, Text, TokenText};
use biome_rowan::{AstNode, SyntaxResult, Text, TextRange, TokenText};
use crate::globals::{
GLOBAL_GLOBAL_ID, GLOBAL_INSTANCEOF_PROMISE_ID, GLOBAL_NUMBER_ID, GLOBAL_STRING_ID,
GLOBAL_UNDEFINED_ID,
};
use crate::literal::{BooleanLiteral, NumberLiteral, StringLiteral};
use crate::literal::{BooleanLiteral, NumberLiteral, RegexpLiteral, StringLiteral};
use crate::{
AssertsReturnType, CallArgumentType, Class, Constructor, DestructureField, Function,
FunctionParameter, FunctionParameterBinding, GenericTypeParameter, Interface, Literal, Module,
@ -614,7 +614,7 @@ impl TypeData {
Literal::Number(NumberLiteral::new(text_from_token(expr.value_token())?))
}
AnyJsLiteralExpression::JsRegexLiteralExpression(expr) => {
Literal::RegExp(text_from_token(expr.value_token())?)
Literal::RegExp(split_regex_literal(expr.value_token())?)
}
AnyJsLiteralExpression::JsStringLiteralExpression(expr) => Literal::String(
StringLiteral::from(Text::from(expr.inner_string_text().ok()?)),
@ -2536,6 +2536,28 @@ fn return_type_from_annotation(
.and_then(|ty| ReturnType::from_any_ts_return_type(resolver, scope_id, &ty))
}
#[inline]
fn split_regex_literal(token: SyntaxResult<JsSyntaxToken>) -> Option<RegexpLiteral> {
let literal = token.ok()?.token_text_trimmed();
let open_index: usize = literal.find('/')? + 1;
let close_index: usize = literal.rfind('/')?;
if open_index == close_index {
return None;
}
let literal_len = usize::from(literal.len());
Some(RegexpLiteral {
pattern: literal
.clone()
.slice(TextRange::try_from((open_index, close_index)).ok()?)
.into(),
flags: literal
.slice(TextRange::try_from((close_index + 1, literal_len)).ok()?)
.into(),
})
}
#[inline]
fn text_from_any_js_name(name: AnyJsName) -> Option<Text> {
match name {

View file

@ -22,6 +22,7 @@ use biome_rowan::Text;
use crate::{
ModuleId, Resolvable, ResolvedTypeId, ResolverId, TypeResolver,
globals::{GLOBAL_NUMBER_ID, GLOBAL_STRING_ID, GLOBAL_UNKNOWN_ID},
literal::RegexpLiteral,
type_data::literal::{BooleanLiteral, NumberLiteral, StringLiteral},
};
@ -647,7 +648,7 @@ pub enum Literal {
Boolean(BooleanLiteral),
Number(NumberLiteral),
Object(ObjectLiteral),
RegExp(Text),
RegExp(RegexpLiteral),
String(StringLiteral),
Template(Text), // TODO: Custom impl of PartialEq for template literals
}

View file

@ -81,6 +81,35 @@ impl NumberLiteral {
}
}
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq, Resolvable)]
pub struct RegexpLiteral {
/// The pattern to match against.
///
/// Examples:
///
/// ```js
/// const regex1 = /hello/;
/// // Pattern: ^^^^^
///
/// const regex2 = new RegExp("world", "i");
/// // Pattern: ^^^^^
/// ```
pub pattern: Text,
/// The flags carried by the expression.
///
/// Examples:
///
/// ```js
/// const regex1 = /hello/i;
/// // Flag: ^
///
/// const regex2 = new RegExp("world", "g");
/// // Flag: ^
/// ```
pub flags: Text,
}
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq, Resolvable)]
pub struct StringLiteral(Text);

View file

@ -45,6 +45,17 @@ fn infer_type_of_regex() {
assert_type_data_snapshot(CODE, &ty, &resolver, "infer_type_of_regex");
}
#[test]
fn infer_type_of_regex_with_flags() {
const CODE: &str = r#"/ab+c/gi"#;
let root = parse_ts(CODE);
let expr = get_expression(&root);
let mut resolver = GlobalsResolver::default();
let ty = TypeData::from_any_js_expression(&mut resolver, ScopeId::GLOBAL, &expr);
assert_type_data_snapshot(CODE, &ty, &resolver, "infer_type_of_regex_with_flags");
}
#[test]
fn infer_type_of_typeof_expression() {
const CODE: &str = r#"typeof foo"#;

View file

@ -0,0 +1,16 @@
---
source: crates/biome_js_type_info/tests/utils.rs
expression: content
---
## Input
```ts
/ab+c/gi;
```
## Result
```
regex: /ab+c/gi
```