Skip to content

Commit

Permalink
Initial RegExp (#184)
Browse files Browse the repository at this point in the history
- Add `RegExp` structure
- Add `RegExp.prototype.exec` custom logic
- Add new specification tests
- Don't print [null] as prototype of object

---------

Co-authored-by: Ben <[email protected]>
  • Loading branch information
lemueldls and kaleidawave authored Aug 26, 2024
1 parent 793f54d commit c952b6d
Show file tree
Hide file tree
Showing 18 changed files with 621 additions and 40 deletions.
71 changes: 71 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions checker/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ path-absolutize = { version = "3.0", features = ["use_unix_paths_on_wasm"] }
either = "1.6"
levenshtein = "1"
ordered-float = "4.2"
regress = { version = "0.10.0", features = [] }

serde = { version = "1.0", features = ["derive"], optional = true }
simple-json-parser = "0.0.2"
Expand Down
Binary file modified checker/definitions/internal.ts.d.bin
Binary file not shown.
31 changes: 31 additions & 0 deletions checker/definitions/overrides.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,37 @@ declare class String {

declare class Promise<T> { }

declare class RegExp {
@Constant("regexp:constructor")
constructor(pattern: string, flags?: string);

@Constant("regexp:exec")
exec(input: string): RegExpExecArray | null;
}

// es5
interface RegExpExecArray extends Array<string> {
/**
* The index of the search at which the result was found.
*/
index: number;
/**
* A copy of the search string.
*/
input: string;
/**
* The first match. This will always be present because `null` will be returned if there are no matches.
*/
0: string;
}

// es2018
interface RegExpExecArray {
groups?: {
[key: string]: string;
};
}

type ResponseBody = string;

declare class Response {
Expand Down
63 changes: 56 additions & 7 deletions checker/specification/specification.md
Original file line number Diff line number Diff line change
Expand Up @@ -344,8 +344,8 @@ declare type U = { a: 2 } & { b: 3 }
declare let x: U;
x.b satisfies 3;

({ a: 2, b: 3 } satisfies U);
({ b: 3 } satisfies U);
({ a: 2, b: 3 } satisfies U);
({ b: 3 } satisfies U);
```

- Expected U, found { b: 3 }
Expand Down Expand Up @@ -2433,7 +2433,7 @@ interface X {
- Expected 4, found 5
- Type { a: 3 } is not assignable to type X

#### RegExp
#### `RegExp` constructor

> RegExp = Regular expression
> In the future, their definition could be considered and evaluated at runtime
Expand All @@ -2444,6 +2444,55 @@ const regexp = /hi/ satisfies string;

- Expected string, found /hi/

#### Invalid regular expressions

```ts
const regexp1 = /(?a2)/;
const regexp2 = new RegExp("?a2");
```

- Invalid regular expression: Invalid token at named capture group identifier
- Invalid regular expression: Invalid atom character

#### Constant `RegExp.exec`

```ts
const regexp = /hi/;
const match = regexp.exec("hi");
match satisfies number;
match.index satisfies string;
match.input satisfies boolean;
```

- Expected number, found ["hi"]
- Expected string, found 0
- Expected boolean, found "hi"

#### Constant `RegExp.exec` groups

```ts
const regexp = /Hi (?<name>.*)/;
const match = regexp.exec("Hi Ben");
match.input satisfies number;
match.groups satisfies string;
```

- Expected number, found "Hi Ben"
- Expected string, found { name: "Ben" }

#### Constant `RegExp.exec` groups greedy

```ts
const regexp = /.*(?<x>[a-z]+)(?<y>[0-9]+)/;
const match = regexp.exec("ez as abc123");
match.input satisfies number;
match.groups.x satisfies "c";
match.groups.y satisfies boolean;
```

- Expected number, found "ez as abc123"
- Expected boolean, found "123"

#### Null and undefined

```ts
Expand Down Expand Up @@ -3921,7 +3970,7 @@ function booleanNarrow(param: boolean) {
function operatorNarrows(thing: string | null) {
(thing ?? "something") satisfies string;
(thing || "something") satisfies number;

const result = thing === "hi" && (thing satisfies boolean);
}
```
Expand All @@ -3936,7 +3985,7 @@ function logicNarrow(thing: any, other: any) {
if (typeof thing === "string" && other === 4) {
({ thing, other }) satisfies string;
}

if (typeof thing === "string" || typeof thing === "number") {
thing satisfies null;
}
Expand Down Expand Up @@ -4258,10 +4307,10 @@ proxy1.a satisfies string;

```ts
let lastSet: string = "";
const proxy1 = new Proxy({ a: 2 }, {
const proxy1 = new Proxy({ a: 2 }, {
set(target: { a: number }, prop: string, value: number, receiver) {
lastSet = prop;
}
}
});

proxy1.a = 6;
Expand Down
3 changes: 3 additions & 0 deletions checker/src/context/environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1367,6 +1367,9 @@ impl<'a> Environment<'a> {
"Boolean" => {
return Ok(TypeId::BOOLEAN_TYPE);
}
"RegExp" => {
return Ok(TypeId::REGEXP_TYPE);
}
"Function" => {
return Ok(TypeId::FUNCTION_TYPE);
}
Expand Down
1 change: 1 addition & 0 deletions checker/src/context/root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ impl RootContext {
("void".to_owned(), TypeId::VOID_TYPE),
("Array".to_owned(), TypeId::ARRAY_TYPE),
("Promise".to_owned(), TypeId::PROMISE_TYPE),
("RegExp".to_owned(), TypeId::REGEXP_TYPE),
("ImportMeta".to_owned(), TypeId::IMPORT_META),
("Function".to_owned(), TypeId::FUNCTION_TYPE),
("object".to_owned(), TypeId::OBJECT_TYPE),
Expand Down
24 changes: 20 additions & 4 deletions checker/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ pub struct TDZ {
pub position: SpanWithSource,
}

pub struct InvalidRegexp {
pub error: String,
pub position: SpanWithSource,
}

pub struct NotInLoopOrCouldNotFindLabel {
pub label: Label,
pub position: SpanWithSource,
Expand Down Expand Up @@ -452,6 +457,7 @@ pub(crate) enum TypeCheckError<'a> {
position: SpanWithSource,
},
CannotDeleteProperty(CannotDeleteFromError),
InvalidRegexp(InvalidRegexp),
}

#[allow(clippy::useless_format)]
Expand Down Expand Up @@ -827,7 +833,7 @@ impl From<TypeCheckError<'_>> for Diagnostic {
} => Diagnostic::Position {
reason: match property {
PropertyKeyRepresentation::Type(ty) => format!("Cannot write to property of type {ty}"),
PropertyKeyRepresentation::StringKey(property) => format!("Cannot write to property '{property}'")
PropertyKeyRepresentation::StringKey(property) => format!("Cannot write to property '{property}'")
},
position,
kind,
Expand All @@ -850,7 +856,7 @@ impl From<TypeCheckError<'_>> for Diagnostic {
} => Diagnostic::Position {
reason: match property {
PropertyKeyRepresentation::Type(ty) => format!("Cannot write to property of type {ty} as it is a getter"),
PropertyKeyRepresentation::StringKey(property) => format!("Cannot write to property '{property}' as it is a getter")
PropertyKeyRepresentation::StringKey(property) => format!("Cannot write to property '{property}' as it is a getter")
},
position,
kind,
Expand All @@ -861,12 +867,17 @@ impl From<TypeCheckError<'_>> for Diagnostic {
} => Diagnostic::Position {
reason: match property {
PropertyKeyRepresentation::Type(ty) => format!("Cannot write to non-existent property of type {ty}"),
PropertyKeyRepresentation::StringKey(property) => format!("Cannot write to non-existent property '{property}'")
PropertyKeyRepresentation::StringKey(property) => format!("Cannot write to non-existent property '{property}'")
},
position,
kind,
}
}
},
TypeCheckError::InvalidRegexp(InvalidRegexp { error, position }) => Diagnostic::Position {
reason: format!("Invalid regular expression: {error}"),
position,
kind,
},
}
}
}
Expand Down Expand Up @@ -1176,5 +1187,10 @@ fn function_calling_error_diagnostic(
kind,
}
}
FunctionCallingError::InvalidRegexp(InvalidRegexp { error, position }) => Diagnostic::Position {
reason: format!("Invalid regular expression: {error}"),
position,
kind,
}
}
}
Loading

0 comments on commit c952b6d

Please sign in to comment.