diff --git a/src/content/docs/curriculum-help.mdx b/src/content/docs/curriculum-help.mdx index c8e7334d..a84badee 100644 --- a/src/content/docs/curriculum-help.mdx +++ b/src/content/docs/curriculum-help.mdx @@ -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 +``` + +#### `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 ``), and `typeArguments` returns generic usages (like ``). + +```js +const explorer = await __helpers.Explorer( + 'function id(x: T): T { return x; }' +); +const { id } = explorer.functions; +id.typeParameters[0].matches('T'); // true + +const explorer2 = await __helpers.Explorer( + 'const m: Map = 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 +```