Skip to content
Open
Changes from all commits
Commits
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
359 changes: 359 additions & 0 deletions src/content/docs/curriculum-help.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1245,3 +1245,362 @@ def add(
):
return x + y
```

## TypeScript Helpers

These helpers provide Abstract Syntax Tree (AST) analysis capabilities for TypeScript challenges.

### Basic Usage

`Explorer` is a chainable class that allows you to call methods on the result of parsing a string. Here's how to create an instance of `Explorer` that parses the camper's code:

```js
const explorer = await __helpers.Explorer(code);
```

To access a specific statement within the code you need to chain method calls until you reach the desired scope. For example, if the camper has written the following code:

```ts
class Spam {
get42(): number {
return 42;
}
}
```

To check the return type annotation you would write:

```js
const explorer = await __helpers.Explorer(code);
assert.isTrue(
explorer.classes.Spam.methods.get42.hasReturnAnnotation('number')
);
```

### Methods and Properties

#### `isEmpty()`

Returns `true` if the Explorer instance has no AST node, otherwise returns `false`.

```js
const explorer = await __helpers.Explorer('const a = 1;');
explorer.isEmpty(); // false
```

#### `toString()`

Returns the source code representation of the current node, or `"no ast"` if the node is empty.

```js
const explorer = await __helpers.Explorer('const a = 1;');
explorer.toString(); // "const a = 1;"
```

#### `matches(other: string | Explorer)`

Compares the current tree with another tree or string, ignoring semicolons and irrelevant whitespace differences.

```js
const explorer = await __helpers.Explorer('const a = 1;');
explorer.matches('const a = 1'); // true (ignores whitespace and semicolons)
explorer.matches('const b = 1;'); // false
```

#### `variables`

Returns an object mapping variable names to `Explorer` instances for all variables in the current scope.

```js
const explorer = await __helpers.Explorer(
'var a = 1; let b = 2; const c = () => {}'
);
const { a, b, c } = explorer.variables; // { a: Explorer, b: Explorer, c: Explorer }
a.matches('var a = 1;'); // true
b.matches('let b = 2;'); // true
c.matches('const c = () => {}'); // true
```

#### `value`

Returns an `Explorer` instance representing the assigned value of a variable, property, parameter, or property assignment. Returns an empty `Explorer` if there is no initializer.

```js
const explorer = await __helpers.Explorer('const a = 1; const b = { x: 10 };');
const { a, b } = explorer.variables;
a.value.toString(); // "1"
b.value.matches('{ x: 10 }'); // true
```

#### `objectProps`

Returns an object mapping property names to `Explorer` instances for all properties in an object literal.

```js
const explorer = await __helpers.Explorer(
"const obj = { x: 1, y: 'hello', z: true };"
);
const { obj } = explorer.variables;
const props = obj.value.objectProps; // { x: Explorer, y: Explorer, z: Explorer }
```

#### `functions` and `allFunctions`

`functions` returns function declarations only. `allFunctions` also includes arrow functions and function expressions assigned to variables.

```js
const explorer = await __helpers.Explorer(
'function foo() { return 42; } const bar = () => 24;'
);
explorer.functions; // { foo: Explorer }
explorer.allFunctions; // { foo: Explorer, bar: Explorer }
```

#### `parameters`

Returns an array of `Explorer` instances representing the parameters of a function or method.

```js
const explorer = await __helpers.Explorer(
'function foo(x: number, y: string) { return 42; }'
);
const parameters = explorer.functions.foo.parameters;
parameters[0].toString(); // x: number
parameters[1].toString(); // y: string
```

#### `annotation`

Returns an `Explorer` instance representing the type annotation of the current node, or an empty `Explorer` if none exists.

```js
const explorer = await __helpers.Explorer(
'const a: number = 1; const b: { age: number } = { age: 33 }'
);
const { a, b } = explorer.variables;
a.annotation; // Explorer with "number"
b.annotation; // Explorer with "{ age: number }"
```

#### `hasAnnotation(annotation: string)`

Returns `true` if the current node has a type annotation matching the specified string.

```js
const explorer = await __helpers.Explorer('const a: number = 1;');
const { a } = explorer.variables;
a.hasAnnotation('number'); // true
a.hasAnnotation('string'); // false
```

#### `hasReturnAnnotation(annotation: string)`

Returns `true` if the function/method has a return type annotation matching the specified type.

```js
const explorer = await __helpers.Explorer(
'function foo(): number { return 42; }'
);
const { foo } = explorer.functions;
foo.hasReturnAnnotation('number'); // true
foo.hasReturnAnnotation('string'); // false
```

#### `hasReturn(value: string)`

Checks whether a function, method, or function-valued variable has a matching top-level return expression.

```js
const explorer = await __helpers.Explorer('function foo() { return 42; }');
const { foo } = explorer.functions;
foo.hasReturn('42'); // true
```

#### `types`

Returns an object mapping type alias names to `Explorer` instances for all type aliases in the current scope.

```js
const explorer = await __helpers.Explorer(
'type Foo = { x: number; }; type Bar = { y: string; };'
);
const { types } = explorer; // { Foo: Explorer, Bar: Explorer }
types.Foo.matches('type Foo = { x: number; };'); // true
types.Bar.matches('type Bar = { y: string; };'); // true
```

#### `interfaces`

Returns an object mapping interface names to `Explorer` instances for all interfaces in the current scope.

```js
const explorer = await __helpers.Explorer(
'interface Foo { x: number; } interface Bar { y: string; }'
);
const { interfaces } = explorer; // { Foo: Explorer, Bar: Explorer }
interfaces.Foo.matches('interface Foo { x: number; }'); // true
interfaces.Bar.matches('interface Bar { y: string; }'); // true
```

#### `classes`

Returns an object mapping class names to `Explorer` instances for all classes in the current scope.

```js
const explorer = await __helpers.Explorer(
'class Foo { x: number; } class Bar { y: string; }'
);
const { classes } = explorer; // { Foo: Explorer, Bar: Explorer }
classes.Foo.matches('class Foo { x: number; }'); // true
classes.Bar.matches('class Bar { y: string; }'); // true
```

#### `methods`

Returns an object mapping method names to `Explorer` instances for all methods in the current class.

```js
const explorer = await __helpers.Explorer(
'class Foo { method1() {} method2() {} }'
);
const { Foo } = explorer.classes;
const { method1, method2 } = Foo.methods; // { method1: Explorer, method2: Explorer }
```

#### `classConstructor`, `classProps`, and `constructorProps`

Use these to inspect class constructors and class-related properties.

```js
const explorer = await __helpers.Explorer(
'class Foo { prop1: number; constructor(x) { this.y = x; } }'
);
const { Foo } = explorer.classes;
Foo.classConstructor?.matches('constructor(x) { this.y = x; }'); // true
Foo.classProps; // { prop1: Explorer }
Foo.constructorProps; // { y: Explorer }
```

#### `typeProps`

Returns an object mapping property names to `Explorer` instances for all properties in a type, interface, or type literal.

```js
const explorer = await __helpers.Explorer(
'type Foo = { x: number; y: string; };'
);
const { Foo } = explorer.types;
const { x, y } = Foo.typeProps; // { x: Explorer, y: Explorer }
```

#### `hasTypeProps(props: TypeProp | TypeProp[])`

Returns `true` if all specified properties exist in a type, interface, or type literal, with optional type and optionality verification. `TypeProp` is an object with the form `{ name: string; type?: string; isOptional?: boolean }`.

```js
const explorer = await __helpers.Explorer(
'type Foo = { x: number; y: string; z?: boolean; };'
);
const { Foo } = explorer.types;
Foo.hasTypeProps({ name: 'x' }); // true
Foo.hasTypeProps([
{ name: 'x', type: 'number' },
{ name: 'y', type: 'string', isOptional: false },
{ name: 'z', isOptional: true }
]); // true
Foo.hasTypeProps({ name: 'a' }); // false
```

#### `isUnionOf(types: string[])`

Returns `true` if the current node has a union type annotation whose members exactly match the specified types, ignoring order.

```js
const explorer = await __helpers.Explorer(
'const a: number | string | boolean;'
);
const { a } = explorer.variables;
a.annotation.isUnionOf(['number', 'string', 'boolean']); // true
a.annotation.isUnionOf(['number', 'string']); // false
```
Comment on lines +1514 to +1525
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not completely obvious that this is the expected behaviour, so I would update the wording.


#### `hasCast(expectedType?: string)`

Checks if the current node is a type assertion (cast using `as`). Optionally verify the cast type matches the provided type string.

```js
const explorer = await __helpers.Explorer(
'const a = 1 as number; const b = 2 as string;'
);
const { a, b } = explorer.variables;
a.value.hasCast(); // true
a.value.hasCast('number'); // true
a.value.hasCast('string'); // false
b.value.hasCast('string'); // true
```

#### `hasNonNullAssertion()`

Checks whether the current expression is a non-null assertion (`!`).

```js
const explorer = await __helpers.Explorer('const a = maybeValue!;');
const { a } = explorer.variables;
a.value.hasNonNullAssertion(); // true
```

#### `doesExtend(basesToCheck: string | string[])`

Checks if a class or interface extends the specified base class or interface(s). Accepts a single string or an array of strings.

```js
const explorer = new Explorer('interface Foo extends Bar, Baz { }');
const { Foo } = explorer.interfaces;
Foo.doesExtend('Bar'); // true
Foo.doesExtend(['Bar', 'Baz']); // true only if both are extended
Foo.doesExtend('Spam'); // false
```

#### `doesImplement(basesToCheck: string | string[])`

Checks if a class implements the specified interface(s). Accepts a single string or an array of strings.

```js
const explorer = await __helpers.Explorer('class Foo implements Bar, Baz { }');
const { Foo } = explorer.classes;
Foo.doesImplement('Bar'); // true
Foo.doesImplement(['Bar', 'Baz']); // true
Foo.doesImplement('Spam'); // false
```

#### `typeParameters` and `typeArguments`

`typeParameters` returns generic declarations (like `<T>`), and `typeArguments` returns generic usages (like `<string>`).

```js
const explorer = await __helpers.Explorer(
'function id<T>(x: T): T { return x; }'
);
const { id } = explorer.functions;
id.typeParameters[0].matches('T'); // true

const explorer2 = await __helpers.Explorer(
'const m: Map<string, number> = new Map();'
);
const { m } = explorer2.variables;
m.annotation.typeArguments[0].matches('string'); // true
```

#### Access modifiers

Use `isPrivate()`, `isProtected()`, `isPublic()`, and `isReadOnly()` on class members and type properties.

```js
const explorer = await __helpers.Explorer(
'class Foo { private readonly x: number; }'
);
const { Foo } = explorer.classes;
const { x } = Foo.classProps;
x.isPrivate(); // true
x.isReadOnly(); // true
```
Loading