---
title: "Use the Curriculum Helpers"
---

> Documentation Index
> Fetch the complete documentation index at: https://contribute.freecodecamp.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Use the Curriculum Helpers

The test runner exposes helpers through `__helpers` for testing campers' code.

## General Helpers

### Removing Whitespace

Use `__helpers.removeWhiteSpace(codeString)` to remove all whitespace from code:

```js
const code = 'function test() {\n  return true;\n}';
__helpers.removeWhiteSpace(code); // "functiontest(){returntrue;}"
```

This removes whitespace inside string literals too. For example, `'hello world'` becomes `'helloworld'`.

### `retryingTest`

```js
await __helpers.retryingTest(callback, message, numberOfTries);
```

The callback must synchronously return a truthy value when the condition is met. Otherwise, the helper schedules another attempt with a 1 ms delay. The optional `numberOfTries` defaults to `20`. After all attempts fail, the returned promise rejects with an error containing `message`.

For example, wait for an element that is added after an asynchronous operation:

```js
await __helpers.retryingTest(
  () => document.querySelector('.result'),
  'The result element was not found.',
  20
);
```

Do not pass an `async` callback: its promise is truthy, so the helper considers the condition met without awaiting its result. Browser scheduling can make the delay longer than 1 ms.

### Regex Utilities

These helpers build text patterns. Prefer the language-specific helpers below when testing code structure.

#### `.permutateRegex()`

Available via `__helpers.permutateRegex(arrayOfRegexOrStrings, options)`, this method creates a regex that matches elements in any order by generating all possible permutations.

Use a small, nonempty input: _n_ elements produce _n!_ permutations, including duplicates when elements repeat. An empty input produces a pattern that matches an empty string. The helper uses each regex's source and discards its flags. Strings are also treated as regex patterns; use [`escapeRegExp`](#escaperegexp) for literal text containing regex metacharacters.

```javascript
const source1 = 'a';
const regex1 = /b/;
const source2 = 'c';

__helpers.permutateRegex([source1, regex1, source2]).source ===
  new RegExp(
    /(?:a\s*\|\|\s*b\s*\|\|\s*c|b\s*\|\|\s*a\s*\|\|\s*c|c\s*\|\|\s*a\s*\|\|\s*b|a\s*\|\|\s*c\s*\|\|\s*b|b\s*\|\|\s*c\s*\|\|\s*a|c\s*\|\|\s*b\s*\|\|\s*a)/
  ).source; // true
```

The method supports named capturing groups and backreferences:

```javascript
const regex = __helpers.permutateRegex(['a', /(?<ref>'|"|`)b\k<ref>/], {
  elementsSeparator: String.raw`\s*===\s*`
});

regex.source ===
  new RegExp(
    /(?:a\s*===\s*(?<ref_0>'|"|`)b\k<ref_0>|(?<ref_1>'|"|`)b\k<ref_1>\s*===\s*a)/
  ).source; // true

regex.test('a === "b"'); // true
regex.test("'b' === a"); // true
regex.test('a === `b`'); // true
regex.test(`a === 'b"`); // false
```

##### Options

- `capture: boolean` - Whole regex is wrapped in regex group. If `capture` is `true` the group will be capturing, otherwise it will be non-capturing. Defaults to `false`.
- `elementsSeparator: string` - Separates permuted elements within single permutation. Defaults to `\s*\|\|\s*`.
- `permutationsSeparator: string` - Separates permutations. Defaults to `|`.

```javascript
__helpers.permutateRegex(['a', /b/, 'c'], { capture: true }).source ===
  new RegExp(
    /(a\s*\|\|\s*b\s*\|\|\s*c|b\s*\|\|\s*a\s*\|\|\s*c|c\s*\|\|\s*a\s*\|\|\s*b|a\s*\|\|\s*c\s*\|\|\s*b|b\s*\|\|\s*c\s*\|\|\s*a|c\s*\|\|\s*b\s*\|\|\s*a)/
  ).source; // true
```

```javascript
__helpers.permutateRegex(['a', /b/, 'c'], { elementsSeparator: ',' }).source ===
  new RegExp(/(?:a,b,c|b,a,c|c,a,b|a,c,b|b,c,a|c,b,a)/).source; // true
```

```javascript
__helpers.permutateRegex(['a', /b/, 'c'], { permutationsSeparator: '&' })
  .source ===
  new RegExp(
    /(?:a\s*\|\|\s*b\s*\|\|\s*c&b\s*\|\|\s*a\s*\|\|\s*c&c\s*\|\|\s*a\s*\|\|\s*b&a\s*\|\|\s*c\s*\|\|\s*b&b\s*\|\|\s*c\s*\|\|\s*a&c\s*\|\|\s*b\s*\|\|\s*a)/
  ).source; // true
```

#### `.concatRegex()`

Available via `__helpers.concatRegex(regexOrString1, regexOrString2, ...)`, this method combines and returns a compiled regex when given two or more regexes or strings.

Input regex flags are not preserved. Strings are treated as regex patterns, not escaped literal text. The combined pattern does not parse code or handle nested structures for you.

For example, if `.concatRegex` is called with `.*` and `b\s` the resulting regex will be equivalent to `.*b\s`.

```js
const regex1 = /a\s/;
const regex2 = /b/;
__helpers.concatRegex(regex1, regex2).source === 'a\\sb'; // true
```

#### `.escapeRegExp()`

Available via `__helpers.escapeRegExp(string)`, this method escapes special regular expression characters in a string.

```js
__helpers.escapeRegExp('hello.world'); // "hello\\.world"
__helpers.escapeRegExp('test[123]'); // "test\\[123\\]"
```

Use this for literal text that will be inserted into a regex. It does not parse code or make a pattern capable of matching balanced braces.

## HTML Helper

### Removing HTML Comments

Use `__helpers.removeHtmlComments(codeString)` to remove HTML comments (`<!-- -->`) from code:

```js
const htmlCode = '<div>Hello <!-- this is a comment --> World</div>';
__helpers.removeHtmlComments(htmlCode); // "<div>Hello  World</div>"
```

This is a text replacement, not an HTML parser. It also removes comment-like text inside attribute values. For example, `'<div title="<!-- note -->">'` becomes `'<div title="">'`. Use the DOM to inspect elements and attributes.

## CSS Helper

### CSS Helper Class

To instantiate the helper within a test block, use this:

```js
const helper = new __helpers.CSSHelp(document);
```

In that example, the `document` variable refers to the document object of the test runner's iframe.

`CSSHelp` reads authored rules from the stylesheet selected by `getStyleSheet()`, not computed styles or every stylesheet in the document. Selector lookups match the serialized selector text exactly; `getStyle('p')` does not match a rule whose selector is `p, div`. Ordinary style lookups inspect top-level rules only. Use the media-query helpers to inspect rules inside `@media`.

### Methods

There are a few methods available for parsing and testing CSS.

#### `.getStyle()`

The `.getStyle()` method takes a CSS selector and returns the first matching CSS style declaration object, or `null` if no rule matches.

For example, if the camper has written the following CSS:

```css
body {
  background-color: rgb(118, 201, 255);
  width: 100%;
}
```

There are three ways to retrieve CSS values from the returned object:

| Method                       | Example                                              | Returns  |
| ---------------------------- | ---------------------------------------------------- | -------- |
| Direct property access       | `helper.getStyle('body')?.width`                     | `'100%'` |
| Native `.getPropertyValue()` | `helper.getStyle('body')?.getPropertyValue('width')` | `'100%'` |
| Helper `.getPropVal()`       | `helper.getStyle('body')?.getPropVal('width')`       | `'100%'` |

`.getPropertyValue()` is a native CSS style declaration object method for reading CSS properties by their kebab-case name, such as `background-color`. See [MDN: CSSStyleDeclaration.getPropertyValue()](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration/getPropertyValue).

The `.getStyle()` helper attaches `.getPropVal()` to the declaration. It wraps `getPropertyValue()` and accepts an optional second argument that removes all whitespace when set to `true`:

```js
const style = helper.getStyle('body');
assert.exists(style);
assert.strictEqual(style.getPropVal('background-color'), 'rgb(118, 201, 255)');
assert.strictEqual(
  style.getPropVal('background-color', true),
  'rgb(118,201,255)'
);
```

Whitespace stripping also changes quoted values. A property that is not set in the declaration returns an empty string; inherited or computed values are not supplied. Gradient colors belong to `background-image`, not `background-color`.

#### `.getStyleAny()`

The `.getStyleAny()` method takes one array of selectors and returns the first matching style declaration in that array's order, or `null` if none match. It does not merge declarations or choose the rule that wins in the cascade.

For example, if the camper has written the following CSS:

```css
.sky {
  background-color: #ffcf33;
  width: 100%;
}
```

There are three ways to retrieve CSS values from the returned object:

| Method                       | Example                                                             | Returns  |
| ---------------------------- | ------------------------------------------------------------------- | -------- |
| Direct property access       | `helper.getStyleAny(['.earth', '.sky'])?.width`                     | `'100%'` |
| Native `.getPropertyValue()` | `helper.getStyleAny(['.earth', '.sky'])?.getPropertyValue('width')` | `'100%'` |
| Helper `.getPropVal()`       | `helper.getStyleAny(['.earth', '.sky'])?.getPropVal('width')`       | `'100%'` |

The returned declaration includes the same `.getPropVal()` method as `.getStyle()`:

```js
const style = helper.getStyleAny(['.earth', '.sky']);
assert.exists(style);
assert.strictEqual(style.getPropVal('background-color'), 'rgb(255, 207, 51)');
assert.strictEqual(
  style.getPropVal('background-color', true),
  'rgb(255,207,51)'
);
```

#### `.getCSSRules()`

The `.getCSSRules()` method takes an at-rule type from `media`, `fontface`, `import`, or `keyframes`, and returns an array of matching top-level CSS rules. With no argument, it returns all top-level rules. It returns an empty array when there are no matching rules.

For example, if the camper has written the following CSS:

```css
@media (max-width: 100px) {
  body {
    background-color: green;
  }
}
```

Find the media rule by its condition, then inspect its nested rules:

```js
const mediaRule = helper
  .getCSSRules('media')
  .find(rule => rule.conditionText === '(max-width: 100px)');
assert.exists(mediaRule);
const bodyRule = Array.from(mediaRule.cssRules).find(
  rule => rule.selectorText === 'body'
);
assert.exists(bodyRule);
assert.strictEqual(bodyRule.style.backgroundColor, 'green');
```

A media rule has a `cssRules` collection, not a `style` property. The helper does not recursively search nested at-rules.

#### `.getRuleListsWithinMedia()`

The `.getRuleListsWithinMedia()` method takes exact serialized media text, such as `'(max-width: 200px)'`, and returns an array of rules from the first matching top-level media query. It returns an empty array if none match.

The return result is the equivalent of that media rule's `cssRules` property from the return value of `.getCSSRules("media")`.

### Less Frequent Methods

These methods are not as commonly used, but are available if needed.

#### `.getStyleDeclarations()`

The `.getStyleDeclarations()` method takes a CSS selector and returns an array of all matching top-level CSS style declarations, or an empty array if none match. Unlike `.getStyle()`, it does not attach `.getPropVal()` to the declarations; use the native `.getPropertyValue()` method.

#### `.isPropertyUsed()`

The `.isPropertyUsed()` method takes a CSS property and checks whether it has a nonempty value in any top-level style rule in the selected stylesheet. It does not inspect declarations inside media queries or other nested rules.

#### `.getStyleRule()`

The `.getStyleRule()` method takes a CSS selector and returns an extended rule object with an `.isDeclaredAfter()` method, or `null` if no rule matches. It is not the style declaration returned by `.getStyle()`. `.isDeclaredAfter(selector)` compares the positions of the first matching top-level rules in the stylesheet. It returns `false` if the other selector is absent and does not compare specificity or computed styles.

#### `.getStyleSheet()`

The `.getStyleSheet()` method returns the selected `CSSStyleSheet`, or `null` if none is found. It checks a linked stylesheet whose URL contains `styles` and has rules, then `style.fcc-injected-styles`, then a `style` element without `class` or `media` attributes. It does not combine multiple stylesheets.

#### `.selectorsFromSelector()`

The `.selectorsFromSelector()` method generates tag-based selectors from matching elements and their ancestors. It returns an empty array when no elements match. It does not enumerate every equivalent selector or use element IDs and classes, so do not treat the result as a complete list of valid selectors.

For example if `a` is passed in a selector, it will return an array filled with several alternate selectors based
on what's nearby. The selectors could include `a`, `label > a` , `label a` , `form label a` and `body form label a`.

### Removing CSS Comments

Use `__helpers.removeCssComments(codeString)` to remove CSS-style comments (`/* */`) from code:

```js
const cssCode = 'body { color: red; /* this is a comment */ }';
__helpers.removeCssComments(cssCode); // "body { color: red;  }"
```

This is a regex replacement, not a CSS parser. For example, it changes `content: "/* text */"` to `content: ""`. Empty comments such as `/**/` are not removed. Prefer `CSSHelp` when checking declarations.

## JS Helper

### The `Explorer` Class

Prefer `Explorer` when testing JavaScript code structure. It parses nested blocks, strings, and template literals instead of trying to identify them with regex.

The `Explorer` class is documented under [TypeScript Helpers](#typescript-helpers), where it is used to test TypeScript challenges. Many of its helpers, however, are not about TypeScript-specific features: they inspect language constructs that also exist in vanilla JavaScript, such as variables, functions, and classes. Those helpers can be used to test JavaScript challenges too. They are:

- [`isEmpty()`](#isempty)
- [`toString()`](#tostring)
- [`matches()`](#matchesother-string--explorer)
- [`variables`](#variables)
- [`value`](#value)
- [`objectProps`](#objectprops)
- [`functions` and `allFunctions`](#functions-and-allfunctions)
- [`parameters`](#parameters)
- [`hasReturn()`](#hasreturnvalue-string)
- [`classes`](#classes)
- [`methods`](#methods-1)
- [`classConstructor`, `classProps`, and `constructorProps`](#classconstructor-classprops-and-constructorprops)
- [`doesExtend()`](#doesextendbasestocheck-string--string)

### Mockers

These mock methods that can prove to be unpredictable for testing purposes.

#### RandomMocker

Available via `__helpers.RandomMocker`, this class mocks `Math.random` for testing purposes. Each time `mock()` is called the pseudo-random number generator is reset to its initial state, so that the same sequence of random numbers is generated each time. `restore()` restores the `Math.random` function saved when the mocker was constructed.

```js
const randomMocker = new __helpers.RandomMocker();
randomMocker.mock();
try {
  Math.random(); // first call is always 0.2523451747838408
  Math.random(); // second call is always 0.08812504541128874
  randomMocker.mock();
  Math.random(); // generator is reset, so we get 0.2523451747838408 again
} finally {
  randomMocker.restore();
}
```

The replacement affects every `Math.random()` call in the test environment while active. It does not affect a reference to the original function saved before `mock()` was called.

#### spyOn

Available via `__helpers.spyOn`, this function wraps an object's property in a spy object that records calls and returns.

The spy's `calls` property is an array of arrays, each containing the arguments passed to the method in the order it was called.

The spy's `returns` property is an array of the return values of the method.

Call `restore()` on the spy object to restore the original method.

```js
const obj = { method: (arg1, arg2) => `called by: ${arg1} + ${arg2}` };
const spy = __helpers.spyOn(obj, 'method');
try {
  obj.method('arg', 'another arg');
  obj.method('2nd call');
  spy.calls; // [["arg", "another arg"], ["2nd call"]]
  spy.returns; // ["called by: arg + another arg", "called by: 2nd call + undefined"]
} finally {
  spy.restore();
}
```

Because the spy calls the original method with `.apply(this, args)`, `this` is preserved. This lets you spy on a shared prototype method to detect that an instance method was used without knowing the variable's name. For example, to check that `.map()` was called on an array:

```js
const spy = __helpers.spyOn(Array.prototype, 'map');
try {
  const double = value => value * 2;
  const result = [1, 2].map(double);
  assert.strictEqual(spy.calls[0][0], double);
  assert.deepEqual(spy.returns[0], [2, 4]);
  assert.deepEqual(result, [2, 4]);
} finally {
  spy.restore();
}
```

Do not use `spyOn(Array.prototype, 'push')`. The spy records calls using `calls.push(args)`, so replacing the prototype's `push` causes infinite recursion and a stack overflow. Spying on one known array's `push` works because the spy's internal arrays retain their original method:

```js
const arr = [1, 2];
const spy = __helpers.spyOn(arr, 'push');
try {
  arr.push(3);
  assert.deepEqual(spy.calls, [[3]]);
  assert.deepEqual(spy.returns, [3]);
  assert.deepEqual(arr, [1, 2, 3]);
} finally {
  spy.restore();
}
```

The spy records calls made through the replaced property while it is active. It does not intercept previously saved references to the original method, record the receiving instance, or identify which source variable was used. A prototype spy can also observe unrelated calls from other arrays in the same environment. Return values are recorded as returned; promises are not awaited, and thrown errors are not added to `returns`.

#### spyOnCallbacks

Available via `__helpers.spyOnCallbacks`, this function wraps a method in a spy object that records calls to callbacks passed to that method.

The spy's `callbackSpies` property is an array of arrays, where the inner arrays
contain spy objects. One for each callback passed to the method. Each time the
method is called, a new array of spies is created. i.e. the length of the
`callbackSpies` array is equal to the number of times the spied on method was
called.

The spies are either the original argument (if it was not a function) or a spy
object with `calls` and `returns` properties. The `calls` is populated with the
arguments passed to that callback and the `returns` with the return values of
that callback.

```js
const obj = {
  method: (callback, value) => {
    return arg1 => {
      return callback(arg1, value);
    };
  }
};

const spy = __helpers.spyOnCallbacks(obj, 'method');
try {
  const cb = (arg1, arg2) => {
    return `callback called with: ${arg1} + ${arg2}`;
  };
  const func = obj.method(cb, 'test');
  func('one');
  func('two');

  obj.method(cb, 'test2')('another one');

  spy.callbackSpies[0][0].calls; // [["one", "test"], ["two", "test"]]
  spy.callbackSpies[0][0].returns; // ["callback called with: one + test", "callback called with: two + test"]
  spy.callbackSpies[1][0].calls; // [["another one", "test2"]]
  spy.callbackSpies[1][0].returns; // ["callback called with: another one + test2"]
} finally {
  spy.restore();
}
```

Unlike `spyOn`, `spyOnCallbacks` does not preserve the receiver when calling the original method or its callbacks. Do not use it when either depends on `this`. Callback return values are recorded without awaiting promises. Restoring the method does not undo callback wrappers that the method has already retained.

### Regex-based Helpers

These helpers match text rather than parse JavaScript structure. For AST-based alternatives, see [the `Explorer` class](#the-explorer-class).

#### Removing JavaScript Comments

Use `__helpers.removeJSComments(codeString)` to remove JavaScript comments (`//` and `/* */`) from code:

```js
const jsCode = 'console.log("hello"); // this is a comment';
__helpers.removeJSComments(jsCode); // 'console.log("hello"); '
```

This removes comments without normalizing string quotes or providing an AST. If the comment parser throws, the helper returns the original string, so do not use it as a guarantee that every comment was removed. Prefer `Explorer` for structural checks.

#### `.isCalledWithNoArgs()`

Available via `__helpers.isCalledWithNoArgs(functionName, codeString)`, this method looks for a call with no arguments after removing JavaScript comments.

It only matches calls at the start of a line, allowing leading whitespace, with no whitespace between the name and `(`. It is not a general search for calls inside expressions or blocks.

```js
const code = 'console.log(); alert("test");';
__helpers.isCalledWithNoArgs('console.log', code); // true
__helpers.isCalledWithNoArgs('alert', code); // false
__helpers.isCalledWithNoArgs('console.log', 'const x = 1; console.log();'); // false
__helpers.isCalledWithNoArgs('console.log', 'console.log ();'); // false
```

Use a spy when the requirement is that a method is called during execution. This helper only checks source text and cannot prove that a matching call executes.

#### `.functionRegex()`

Available via `__helpers.functionRegex(functionName, parameterArray, options)`, this method returns a regex that can be used to match function declarations in a code block given a function name and, optionally, a list of parameters.

Prefer [`Explorer.functions` or `Explorer.allFunctions`](#functions-and-allfunctions) to inspect functions. This regex helper does not parse nested blocks, string literals, or template literals.

```js
const regex = __helpers.functionRegex('foo', ['bar', 'baz']);
regex.test('function foo(bar, baz){}'); // true
regex.test('function foo(bar, baz, qux){}'); // false
regex.test('foo = (bar, baz) => {}'); // true
```

Options:

- `capture: boolean` - If `true`, the regex will capture the function definition, including its body, otherwise not. Defaults to `false`.
- `includeBody: boolean` - If `true`, the regex will include the function body in the match. Otherwise it will stop at the first bracket. Defaults to `true`.

```js
const regEx = __helpers.functionRegex('foo', ['bar', 'baz'], { capture: true });
const combinedRegEx = __helpers.concatRegex(/var x = "y";\s*/, regEx);

const match = `var x = "y";
function foo(bar, baz){}`.match(combinedRegEx);
match[1]; // "function foo(bar, baz){}"
// i.e. only the function definition is captured
```

```js
const regEx = __helpers.functionRegex('foo', ['bar', 'baz'], {
  includeBody: false
});

const match = `function foo(bar, baz){console.log('ignored')}`.match(regEx);
match[0]; // "function foo(bar, baz){"
```

For an arrow function with an expression body, the match can continue into statements after the function:

```js
const regEx = __helpers.functionRegex('myFunc', ['arg1'], { capture: true });

const match =
  'myFunc = arg1 => arg1; console.log();\n // captured, unfortunately'.match(
    regEx
  );
match[1]; // "myFunc = arg1 => arg1; console.log();\n // captured, unfortunately"
```

With `includeBody: true`, all supported function forms stop at the first `}` in the body. That brace can belong to a nested block, an object literal, a string, or template interpolation such as `${value}`. Setting `capture: true` exposes the truncated match; it does not make the body extraction complete.

```js
const regEx = __helpers.functionRegex('registerUser', [], { capture: true });

const match = `function registerUser() {
  if (name.trim() === "") {
    throw new Error("Name is required");
  }
  if (age.trim() === "") {
    throw new Error("Age is required");
  }
}`.match(regEx);

match[1].includes('throw new Error("Name is required")'); // true
match[1].includes('age.trim'); // false
```

Use `Explorer` to find the function and inspect its structure instead. This example checks a return containing template interpolation after a nested block:

```js
const explorer = await __helpers.Explorer(`
function describe(value) {
  if (value) {
    console.log(value);
  }
  return \`Value: \${value}\`;
}
`);
const fn = explorer.functions.describe;
assert.exists(fn);
assert.isTrue(fn.hasReturn('`Value: ${value}`'));
```

`includeBody: false` avoids body extraction, but still uses regex to identify the function header. It does not handle every parameter syntax or distinguish code from text inside comments and strings.

#### `.getFunctionParams()`

Available via `__helpers.getFunctionParams(codeString)`, this method extracts parameter information from a function definition string.

```js
const funcString = 'function test(a, b = "default", c) { return a + b + c; }';
const params = __helpers.getFunctionParams(funcString);
// params = [
//   { name: 'a', defaultValue: undefined },
//   { name: 'b', defaultValue: 'default' },
//   { name: 'c', defaultValue: undefined }
// ]
```

This helper splits parameter text on commas and `=` characters, strips destructuring brackets and braces, and removes quote characters from default values. It does not parse nested defaults, destructuring, or strings containing those separators. For example, a default value of `"a,b"` is split as though it were two parameters. Prefer [`Explorer.parameters`](#parameters) and inspect each parameter's `value` when checking defaults.

## TypeScript Helpers

`Explorer` provides Abstract Syntax Tree (AST) analysis for TypeScript and JavaScript. The shared methods and TypeScript-specific methods are documented together below.

### 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);
const method = explorer.classes.Spam?.methods.get42;
assert.exists(method);
assert.isTrue(method.hasReturnAnnotation('number'));
```

Named lookups such as `classes.Spam` can return `undefined`, while other operations return an empty `Explorer` or `null` when no node is available. Searches inspect the current scope; drill into functions or classes to inspect their members. `Explorer` does not execute source code or evaluate expressions.

### 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, comments, 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
```

Everything else is part of the tree and has to be identical, including parentheses. Redundant parentheses, and the optional parentheses around a single arrow function parameter, are all significant:

```js
const explorer = await __helpers.Explorer('const f = (x) => x;');
explorer.matches('const f = (x) => x'); // true
explorer.matches('const f = x => x'); // false

const another = await __helpers.Explorer('const a = (1);');
another.matches('const a = 1'); // false
```

String literals are compared by value, so the quote character does not matter. A template literal is a different kind of node, though, and never matches a plain string:

```js
const explorer = await __helpers.Explorer("const a = 'hello';");
explorer.matches('const a = "hello"'); // true
explorer.matches('const a = `hello`'); // false
```

Because of this, `matches()` is a strict check on one exact way of writing an expression. Whenever a step allows more than one valid form, compare against every form, or drill into the tree and assert on the parts that matter:

```js
const validForms = [
  'items.find((item) => item.id === id)',
  'items.find(item => item.id === id)',
  'items.find((item) => { return item.id === id; })',
  'items.find(item => { return item.id === id; })'
];
assert.isTrue(validForms.some(form => found.value.matches(form)));
```

Note that a list like this still ties campers to the parameter name `item` and to an arrow function, so prefer inspecting the tree when the step does not require either.

#### `variables`

Returns an object mapping variable names to `Explorer` instances for variable statements in the current scope. It is not a recursive search of nested scopes.

Only the first declaration in each statement is exposed. For example, `const a = 1, b = 2;` produces an entry for `a`, but not `b`. Destructuring declarations are not supported: `const { x, y } = obj;` produces an entry keyed by `"undefined"`, rather than entries for `x` and `y`.

```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 c = a + 1;'
);
const { a, b, c } = explorer.variables;
a.value.toString(); // "1"
b.value.matches('{ x: 10 }'); // true
c.value.toString(); // "a + 1"
```

#### `objectProps`

Returns an object mapping property names to `Explorer` instances for explicit property assignments with identifier names in an object literal. Shorthand properties, quoted or computed keys, methods, and spreads are not included. For example, `{ x, "y": 2, z: 3 }` produces only an entry for `z`.

```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].matches('x: number'); // true
parameters[1].matches('y: string'); // true
```

#### `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.

It does not search returns inside nested `if` statements or loops, and it does not prove that the return executes.

```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.isUnionOf(['number', 'string', 'boolean']); // true
a.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 = await __helpers.Explorer('class Dog extends Animal {}');
const { Dog } = explorer.classes;
Dog.doesExtend('Animal'); // true
Dog.doesExtend('Cat'); // 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
```

## Python Helper

Use the Python AST-based helpers to inspect code structure. JavaScript utilities under `__helpers.python` are documented later for cases that need text processing.

### Python AST-based Helpers

These helpers parse Python source into an AST without executing it.

#### Basic Usage Pattern

For Python challenges, these helpers need to be run in Python and tests should be formatted as:

```js
({ test: () => assert(runPython(`<python code>`)) });
```

For complex tests, you can use `assert` inside Python and avoid the `assert` around `runPython`.

```js
({ test: () => runPython(`<python code>`) });
```

An example would be if you need to test multiple things at once.

```js
({
  test: () =>
    runPython(`
assert _Node(_code).find_class("Spam").find_function("__str__").has_return("'spam!'")
s = Spam()
assert f'{s}' == 'spam!'
`)
});
```

`_Node` is a chainable class that allows you to call methods on the result of parsing a string. To create an instance of `_Node` that parses the camper's code use `_Node(_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:

```py
class Spam:
  def __str__(self):
    return 'spam!'
```

To check the return value of `__str__` you would write:

```js
({
  test: () =>
    assert(
      runPython(
        `_Node(_code).find_class("Spam").find_function("__str__").has_return("'spam!'")`
      )
    )
});
```

AST-based helpers are also exposed in the Python challenges in `ast_helpers` module. `_Node` class corresponds to `ast_helpers.Node`:

```py
import ast_helpers

code = '''
class Spam:
  def __str__(self):
    return 'spam!'
'''

print(
    ast_helpers.Node(code)
    .find_class("Spam")
    .find_function("__str__")
    .has_return("'spam!'")
)
```

The standalone Python examples below assume `from ast_helpers import Node`. In challenge tests, use the provided `_Node` and `_code` names. The helpers require parseable Python; invalid syntax raises `SyntaxError`.

### Methods

#### Formatting output

`str` returns a string that would parse to the same AST as the node. For example:

```python
function_str = """
def foo():
  # will not be in the output
  x = 1

"""
output_str = """def foo():
    x = 1"""
str(Node(function_str)) == output_str # True
```

The output and source string compile to the same AST, but the output is indented with 4 spaces. Comments and trailing whitespace are removed.

#### Finding nodes

Most `find_` functions search the current scope rather than recursively searching nested functions or blocks. Drill into the relevant node before continuing. They return one of the following:

- A single Node object if there can be only one match. E.g. `find_function`
- A list of Node objects if there can be multiple matches. E.g.: `find_ifs`

When no match exists, a single-node lookup returns an empty `Node`, while a list lookup returns an empty list. `find_function` returns the first matching synchronous function; use `find_async_function` for an async function or `find_functions` for all matching definitions.

Continuing a lookup chain on a nonexistent node can raise an error. For example, `Node('x = 1').find_function('missing').find_ifs()` raises `AttributeError`.

#### `find_function()`

```python
Node('def foo():\n  x = "1"').find_function("foo").has_variable("x") # True
```

#### `find_functions()`

```python
code_str = """
class Spam(ABC):
  @property
  @abstractmethod
  def foo(self):
    return self.x

  @foo.setter
  @abstractmethod
  def foo(self, new_x):
    self.x = new_x
"""
explorer = Node(code_str)
len(explorer.find_class("Spam").find_functions("foo")) # 2
explorer.find_class("Spam").find_functions("foo")[0].is_equivalent("@property\n@abstractmethod\ndef foo(self):\n  return self.x") # True
explorer.find_class("Spam").find_functions("foo")[1].is_equivalent("@foo.setter\n@abstractmethod\ndef foo(self, new_x):\n  self.x = new_x") # True
```

#### `find_async_function()`

```python
Node('async def foo():\n  await bar()').find_async_function("foo").is_equivalent("async def foo():\n  await bar()") # True
```

#### `find_awaits()`

```python
code_str = """
async def foo(spam):
  if spam:
    await spam()
  await bar()
  await func()
"""
explorer = Node(code_str)
explorer.find_async_function("foo").find_awaits()[0].is_equivalent("await bar()") # True
explorer.find_async_function("foo").find_awaits()[1].is_equivalent("await func()") # True
explorer.find_async_function("foo").find_ifs()[0].find_awaits()[0].is_equivalent("await spam()") # True
```

#### `find_variable()`

```python
Node("y = 2\nx = 1").find_variable("x").is_equivalent("x = 1")
Node("a: int = 1").find_variable("a").is_equivalent("a: int = 1")
Node("self.spam = spam").find_variable("self.spam").is_equivalent("self.spam = spam")
```

#### `find_variables()`

Returns a list of all variable assignments with the given name (unlike `find_variable` which returns only the first match).

```python
code_str = """
x: int = 0
a.b = 0
x = 5
a.b = 2
x = 10
"""
node = Node(code_str)
len(node.find_variables("x")) # 3
node.find_variables("x")[0].is_equivalent("x: int = 0") # True
node.find_variables("x")[1].is_equivalent("x = 5") # True
node.find_variables("x")[2].is_equivalent("x = 10") # True
len(node.find_variables("a.b")) # 2
node.find_variables("a.b")[0].is_equivalent("a.b = 0") # True
node.find_variables("a.b")[1].is_equivalent("a.b = 2") # True
```

#### `find_aug_variable()`

```python
Node("x += 1").find_aug_variable("x").is_equivalent("x += 1")
Node("x -= 1").find_aug_variable("x").is_equivalent("x -= 1")
```

When the variable is out of scope, `find_variable` returns an `None` node (i.e. `Node()` or `Node(None)`):

```python
Node('def foo():\n  x = "1"').find_variable("x") == Node() # True
```

#### `find_body()`

```python
func_str = """
def foo():
    x = 1"""
Node(func_str).find_function("foo").find_body().is_equivalent("x = 1") # True
```

#### `find_return()`

```python
code_str = """
def foo():
  if x == 1:
    return False
  return True
"""
Node(code_str).find_function("foo").find_return().is_equivalent("return True") # True
Node(code_str).find_function("foo").find_ifs()[0].find_return().is_equivalent("return False") # True
```

#### `find_calls()`

```python
code_str = """
print(1)
print(2)
foo("spam")
obj.foo("spam")
obj.bar.foo("spam")
"""
explorer = Node(code_str)
len(explorer.find_calls("print")) # 2
explorer.find_calls("print")[0].is_equivalent("print(1)")
explorer.find_calls("print")[1].is_equivalent("print(2)")
len(explorer.find_calls("foo")) # 3
explorer.find_calls("foo")[0].is_equivalent("foo('spam')")
explorer.find_calls("foo")[1].is_equivalent("obj.foo('spam')")
explorer.find_calls("foo")[2].is_equivalent("obj.bar.foo('spam')")
```

#### `find_call_args()`

```python
explorer = Node("print(1, 2)")
len(explorer.find_calls("print")[0].find_call_args()) # 2
explorer.find_calls("print")[0].find_call_args()[0].is_equivalent("1")
explorer.find_calls("print")[0].find_call_args()[1].is_equivalent("2")
```

#### `find_class()`

```python
class_str = """
class Foo:
  def __init__(self):
    pass
"""
Node(class_str).find_class("Foo").has_function("__init__") # True
```

#### `find_ifs()`

```python
if_str = """
if x == 1:
  x += 1
elif x == 2:
  pass
else:
  return

if True:
  pass
"""

Node(if_str).find_ifs()[0].is_equivalent("if x == 1:\n  x += 1\nelif x == 2:\n  pass\nelse:\n  return")
Node(if_str).find_ifs()[1].is_equivalent("if True:\n  pass")
```

#### `find_if()`

```python
if_str = """
if x == 1:
  x += 1
elif x == 2:
  pass
else:
  return

if True:
  pass
"""

Node(if_str).find_if("x == 1").is_equivalent("if x == 1:\n  x += 1\nelif x == 2:\n  pass\nelse:\n  return")
Node(if_str).find_if("True").is_equivalent("if True:\n  pass")
```

#### `find_whiles()`

```python
while_str = """
while True:
  x += 1
else:
  return

while False:
  pass
"""
explorer = Node(while_str)
explorer.find_whiles()[0].is_equivalent("while True:\n  x += 1\nelse:\n  return") # True
explorer.find_whiles()[1].is_equivalent("while False:\n  pass") # True
```

#### `find_while()`

```python
while_str = """
while True:
  x += 1
else:
  return

while False:
  pass
"""
explorer = Node(while_str)
explorer.find_while("True").is_equivalent("while True:\n  x += 1\nelse:\n  return") # True
explorer.find_while("False").is_equivalent("while False:\n  pass") # True
```

#### `find_conditions()`

```python
if_str = """
if x > 0:
  x = 1
elif x < 0:
  x = -1
else:
  return x
"""
explorer1 = Node(if_str)
len(explorer1.find_ifs()[0].find_conditions()) # 3
explorer1.find_ifs()[0].find_conditions()[0].is_equivalent("x > 0") # True
explorer1.find_ifs()[0].find_conditions()[1].is_equivalent("x < 0") # True
explorer1.find_ifs()[0].find_conditions()[2] == Node() # True
Node("x = 1").find_conditions() # []

while_str = """
while True:
  x += 1
else:
  return

while False:
  pass
"""
explorer2 = Node(while_str)
explorer2.find_whiles()[0].find_conditions()[0].is_equivalent("True") # True
explorer2.find_whiles()[0].find_conditions()[1] == Node() # True
explorer2.find_whiles()[1].find_conditions()[0].is_equivalent("False") # True
```

#### `find_for_loops()`

```python
for_str = """
dict = {'a': 1, 'b': 2, 'c': 3}
for x, y in enumerate(dict):
    print(x, y)
else:
    pass

for i in range(4):
    pass
"""
explorer = Node(for_str)
explorer.find_for_loops()[0].is_equivalent("for x, y in enumerate(dict):\n  print(x, y)\nelse:\n  pass") # True
explorer.find_for_loops()[1].is_equivalent("for i in range(4):\n  pass") # True
```

#### `find_for()`

```python
for_str = """
dict = {'a': 1, 'b': 2, 'c': 3}
for x, y in enumerate(dict):
    print(x, y)
else:
    pass

for i in range(4):
    pass
"""
explorer = Node(for_str)
explorer.find_for("(x, y)", "enumerate(dict)").is_equivalent("for x, y in enumerate(dict):\n  print(x, y)\nelse:\n  pass") # True
explorer.find_for("i", "range(4)").is_equivalent("for i in range(4):\n  pass") # True
```

#### `find_for_vars()`

```python
for_str = """
dict = {'a': 1, 'b': 2, 'c': 3}
for x, y in enumerate(dict):
    print(x, y)
else:
    pass

for i in range(4):
    pass
"""
explorer = Node(for_str)
explorer.find_for_loops()[0].find_for_vars().is_equivalent("(x, y)") # True
explorer.find_for_loops()[1].find_for_vars().is_equivalent("i") # True
```

#### `find_for_iter()`

```python
for_str = """
dict = {'a': 1, 'b': 2, 'c': 3}
for x, y in enumerate(dict):
    print(x, y)
else:
    pass

for i in range(4):
    pass
"""
explorer = Node(for_str)
explorer.find_for_loops()[0].find_for_iter().is_equivalent("enumerate(dict)") # True
explorer.find_for_loops()[1].find_for_iter().is_equivalent("range(4)") # True
```

#### `find_bodies()`

```python
if_str = """
if True:
  x = 1
elif False:
  x = 2
"""
explorer1 = Node(if_str)
explorer1.find_ifs()[0].find_bodies()[0].is_equivalent("x = 1") # True
explorer1.find_ifs()[0].find_bodies()[1].is_equivalent("x = 2") # True

while_str = """
while True:
  x += 1
else:
  x = 0

while False:
  pass
"""
explorer2 = Node(while_str)
explorer2.find_whiles()[0].find_bodies()[0].is_equivalent("x += 1") # True
explorer2.find_whiles()[0].find_bodies()[1].is_equivalent("x = 0") # True
explorer2.find_whiles()[1].find_bodies()[0].is_equivalent("pass") # True

for_str = """
dict = {'a': 1, 'b': 2, 'c': 3}
for x, y in enumerate(dict):
    print(x, y)
else:
    print(x)

for i in range(4):
    pass
"""
explorer3 = Node(for_str)
explorer3.find_for_loops()[0].find_bodies()[0].is_equivalent("print(x, y)") # True
explorer3.find_for_loops()[0].find_bodies()[1].is_equivalent("print(x)") # True
explorer3.find_for_loops()[1].find_bodies()[0].is_equivalent("pass") # True
```

#### `find_imports()`

```python
code_str = """
import ast, sys
from math import factorial as f
"""

explorer = Node(code_str)
len(explorer.find_imports()) # 2
explorer.find_imports()[0].is_equivalent("import ast, sys")
explorer.find_imports()[1].is_equivalent("from math import factorial as f")
```

#### `find_comps()`

Returns a list of list comprehensions, set comprehensions, dictionary comprehensions and generator expressions nodes not assigned to a variable or part of other statements.

```python
code_str = """
[i**2 for i in lst]
(i for i in lst)
{i * j for i in spam for j in lst}
{k: v for k,v in dict}
comp = [i for i in lst]
"""
explorer = Node(code_str)
len(explorer.find_comps()) # 4
explorer.find_comps()[0].is_equivalent("[i**2 for i in lst]")
explorer.find_comps()[1].is_equivalent("(i for i in lst)")
explorer.find_comps()[2].is_equivalent("{i * j for i in spam for j in lst}")
explorer.find_comps()[3].is_equivalent("{k: v for k,v in dict}")
```

#### `find_comp_iters()`

Returns a list of comprehension/generator expression iterables. It can be chained to `find_variable`, `find_return`, `find_call_args()[n]`.

```python
code_str = """
x = [i**2 for i in lst]

def foo(spam):
  return [i * j for i in spam for j in lst]
"""
explorer = Node(code_str)
len(explorer.find_variable("x").find_comp_iters()) # 1
explorer.find_variable("x").find_comp_iters()[0].is_equivalent("lst")

len(explorer.find_function("foo").find_return().find_comp_iters()) # 2
explorer.find_function("foo").find_return().find_comp_iters()[0].is_equivalent("spam")
explorer.find_function("foo").find_return().find_comp_iters()[1].is_equivalent("lst")
```

#### `find_comp_targets()`

Returns a list of comprehension/generator expression targets (i.e. the iteration variables).

```python
code_str = """
x = [i**2 for i in lst]

def foo(spam):
  return [i * j for i in spam for j in lst]
"""
explorer = Node(code_str)
len(explorer.find_variable("x").find_comp_targets()) # 1
explorer.find_variable("x").find_comp_targets()[0].is_equivalent("i")

len(explorer.find_function("foo").find_return().find_comp_targets()) # 2
explorer.find_function("foo").find_return().find_comp_targets()[0].is_equivalent("i")
explorer.find_function("foo").find_return().find_comp_targets()[1].is_equivalent("j")
```

#### `find_comp_key()`

Returns the dictionary comprehension key.

```python
code_str = """
x = {k: v for k,v in dict}

def foo(spam):
  return {k: v for k in spam for v in lst}
"""
explorer = Node(code_str)
explorer.find_variable("x").find_comp_key().is_equivalent("k")

explorer.find_function("foo").find_return().find_comp_key().is_equivalent("k")
```

#### `find_comp_expr()`

Returns the expression evaluated at each iteration of comprehensions/generator expressions. It includes the `if`/`else` portion. In case only the `if` is present, use `find_comp_ifs`.

```python
code_str = """
x = [i**2 if i else -1 for i in lst]

def foo(spam):
  return [i * j for i in spam for j in lst]
"""
explorer = Node(code_str)
explorer.find_variable("x").find_comp_expr().is_equivalent("i**2 if i else -1")

explorer.find_function("foo").find_return().find_comp_expr().is_equivalent("i * j")
```

#### `find_comp_ifs()`

Returns a list of comprehension/generator expression `if` conditions. The `if`/`else` construct instead is part of the expression and is found with `find_comp_expr`.

```python
code_str = """
x = [i**2 if i else -1 for i in lst]

def foo(spam):
  return [i * j for i in spam if i > 0 for j in lst if j != 6]
"""
explorer = Node(code_str)
len(explorer.find_variable("x").find_comp_ifs()) # 0

len(explorer.find_function("foo").find_return().find_comp_ifs()) # 2
explorer.find_function("foo").find_return().find_comp_ifs()[0].is_equivalent("i > 0")
explorer.find_function("foo").find_return().find_comp_ifs()[1].is_equivalent("j != 6")
```

#### `find_trys()`

Returns a list of all try statements.

```python
code_str = """
try:
    x = 1 / 0
except ZeroDivisionError:
    print("division by zero")
else:
    print("no error")
finally:
    print("cleanup")

try:
    y = int("abc")
except:
    pass
"""
node = Node(code_str)
len(node.find_trys()) # 2
node.find_trys()[0].is_equivalent("try:\n  x = 1 / 0\nexcept ZeroDivisionError:\n  print('division by zero')\nelse:\n  print('no error')\nfinally:\n  print('cleanup')") # True
node.find_trys()[1].is_equivalent("try:\n  y = int('abc')\nexcept:\n  pass") # True
```

#### `find_excepts()`

Returns a list of all except handlers in a try statement.

```python
code_str = """
try:
    x = 1 / 0
except ZeroDivisionError:
    print("division by zero")
except ValueError as e:
    print(f"value error: {e}")
except:
    print("other error")
"""
node = Node(code_str)
try_stmt = node.find_trys()[0]
len(try_stmt.find_excepts()) # 3
```

`is_equivalent()` cannot be used on the items found by `find_excepts()` because a standalone `except` raises a `SyntaxError`.

#### `find_except()`

Returns a specific except handler matching the exception type and optional variable name.

```python
code_str = """
try:
    x = 1 / 0
except ZeroDivisionError:
    print("division by zero")
except ValueError as e:
    print(f"value error: {e}")
except:
    print("other error")
"""
node = Node(code_str)
try_stmt = node.find_trys()[0]
try_stmt.find_except("ZeroDivisionError").find_body().is_equivalent("print('division by zero')") # True
try_stmt.find_except("ValueError", "e").find_body().is_equivalent("print(f'value error: {e}')") # True
try_stmt.find_except().find_body().is_equivalent("print('other error')") # True (bare except)
```

#### `has_except()`

Returns True if a try statement has an except handler for the given exception type and variable name.

```python
code_str = """
try:
    x = 1 / 0
except ZeroDivisionError:
    print("division by zero")
except ValueError as e:
    print(f"value error: {e}")
"""
node = Node(code_str)
try_stmt = node.find_trys()[0]
try_stmt.has_except("ZeroDivisionError") # True
try_stmt.has_except("ValueError", "e") # True
try_stmt.has_except("ValueError") # False
```

#### `find_try_else()`

Returns the else block of a try statement.

```python
code_str = """
try:
    x = 1
except ValueError:
    print("error")
else:
    print("success")
    x = 2
"""
node = Node(code_str)
try_stmt = node.find_trys()[0]
try_stmt.find_try_else().is_equivalent("print('success')\nx = 2") # True
```

#### `find_finally()`

Returns the finally block of a try statement.

```python
code_str = """
try:
    x = 1
except ValueError:
    print("error")
finally:
    print("cleanup")
    x = None
"""
node = Node(code_str)
try_stmt = node.find_trys()[0]
try_stmt.find_finally().is_equivalent("print('cleanup')\nx = None") # True
```

#### `find_matches()`

Returns a list of match statements.

```python
code_str = """
match x:
  case 0:
    pass
  case _:
    pass
match y:
  case 1:
    pass
"""
node = Node(code_str)
len(node.find_matches()) # 2
node.find_matches()[0].is_equivalent("match x:\n  case 0:\n    pass\n  case _:\n    pass") # True
```

#### `find_match_subject()`

Returns the subject of a match statement.

```python
code_str = """
match x:
  case 0:
    pass
"""
node = Node(code_str)
node.find_matches()[0].find_match_subject().is_equivalent("x") # True
```

#### `find_match_cases()`

Returns a list of case blocks in a match statement.

```python
code_str = """
match x:
  case 0:
    print(0)
    print('spam')
  case _:
    pass
"""
node = Node(code_str)
len(node.find_matches()[0].find_match_cases()) # 2
node.find_matches()[0].find_match_cases()[0].find_body().is_equivalent("print(0)\nprint('spam')") # True
node.find_matches()[0].find_match_cases()[1].find_body().is_equivalent("pass") # True
```

`is_equivalent()` cannot be used on the items found by `find_match_cases()` because a standalone `case` raises a `SyntaxError`.

#### `find_case_pattern()`

Returns the pattern of a case block.

```python
code_str = """
match x:
  case 0:
    pass
  case [a, b]:
    pass
  case _:
    pass
"""
node = Node(code_str)
node.find_matches()[0].find_match_cases()[0].find_case_pattern().is_equivalent("0") # True
node.find_matches()[0].find_match_cases()[1].find_case_pattern().is_equivalent("[a, b]") # True
node.find_matches()[0].find_match_cases()[2].find_case_pattern().is_equivalent("_") # True
```

#### `find_case_guard()`

Returns the guard condition of a case block.

```python
code_str = """
match x:
  case 0 if y > 0:
    pass
  case [a, b] if y == -1:
    pass
  case _:
    pass
"""
node = Node(code_str)
node.find_matches()[0].find_match_cases()[0].find_case_guard().is_equivalent("y > 0") # True
node.find_matches()[0].find_match_cases()[1].find_case_guard().is_equivalent("y == -1") # True
node.find_matches()[0].find_match_cases()[2].find_case_guard().is_empty() # True (no guard)
```

### Getting values

`get_` functions return the value of the node, not the node itself.

#### `get_variable()`

Returns the value stored in a variable's `ast.Constant` initializer, such as a number, string, boolean, or `None`. It returns `None` if the variable is not found.

```python
Node("x = 1").get_variable("x") # 1
```

The helper reads the initializer's `.value` attribute; it does not evaluate the assignment. Expressions and collections such as `x = 1 + 1`, `x = [1, 2]`, and `x = {"a": 1}` raise `AttributeError` because their AST nodes do not have that attribute. Even `x = -1` raises `AttributeError`: Python represents the minus sign as a unary operation around the constant `1`.

### Checking for existence

`has_` functions return a boolean indicating whether the node exists.

#### `has_variable()`

```python
Node("x = 1").has_variable("x") # True
```

#### `has_function()`

```python
Node("def foo():\n  pass").has_function("foo") # True
```

#### `has_stmt()`

Returns a boolean indicating if the specified statement is found.

```python
Node("name = input('hi')\nself.matrix[1][5] = 3").has_stmt("self.matrix[1][5] = 3") # True
```

#### `has_args()`

```python
Node("def foo(*, a, b, c=0):\n  pass").find_function("foo").has_args("*, a, b, c=0") # True
```

#### `has_pass()`

```python
Node("def foo():\n  pass").find_function("foo").has_pass() # True
Node("if x==1:\n  x+=1\nelse:  pass").find_ifs()[0].find_bodies()[1].has_pass() # True
```

#### `has_return()`

Returns a boolean indicating if the function returns the specified expression/object.

```python
code_str = """
def foo():
  if x == 1:
    return False
  return True
"""
explorer = Node(code_str)
explorer.find_function("foo").has_return("True") # True
explorer.find_function("foo").find_ifs()[0].has_return("False") # True
```

#### `has_returns()`

Returns a boolean indicating if the function has the specified return annotation.

```python
Node("def foo() -> int:\n  return 0").find_function("foo").has_returns("int") # True
Node("def foo() -> 'spam':\n  pass").find_function("foo").has_returns("'spam'") # True
```

#### `has_decorators()`

```python
code_str = """
class A:
  @property
  @staticmethod
  def foo():
    pass
"""
Node(code_str).find_class("A").find_function("foo").has_decorators("property") # True
Node(code_str).find_class("A").find_function("foo").has_decorators("property", "staticmethod") # True
Node(code_str).find_class("A").find_function("foo").has_decorators("staticmethod", "property") # False, order does matter
```

#### `has_call()`

Checks for a matching standalone expression in the current scope. The arguments must match too. It does not recursively search nested statements or find calls inside assignments and return expressions.

```python
code_str = """
print(math.sqrt(25))
if True:
  spam()
"""

explorer = Node(code_str)
explorer.has_call("print(math.sqrt(25))")
explorer.find_ifs()[0].find_bodies()[0].has_call("spam()")
```

#### `block_has_call()`

Searches for a loaded name or attribute within the entire AST, or within the first top-level function selected by `function`. It can find names used in nested expressions, but it does not check that they are the target of a call. For example, `Node('saved = print').block_has_call('print')` returns `True` even though `print` is not called. Aliases are not resolved.

```python
code_str = """
srt = sorted([5, 1, 9])

def foo(lst):
  return sorted(lst)

def spam(lst):
  return lst.sort()

def eggs(dictionary):
  if True:
    k = dictionary.get(key)
"""
node = Node(code_str)

node.block_has_call("sorted", "foo") # True
node.block_has_call("sorted") # True
node.block_has_call("sort", "spam") # True
node.block_has_call("get", "eggs") # True
node.block_has_call("get") # True
node.block_has_call("split") # False
```

#### Asserting absence of a function call

Sometimes a challenge requires learners to remove a specific statement, such as a `print()` call.

Negating `has_call` only excludes the exact standalone expression in the current scope. Negating `block_has_call` also rejects references that are not calls. These checks are not interchangeable:

```python
Node("print(123)").has_call("print()") # False
Node("print(123)").block_has_call("print") # True
Node("saved = print").block_has_call("print") # True
```

#### `has_import()`

```python
code_str = """
import ast, sys
from math import factorial as f
"""

explorer = Node(code_str)
explorer.has_import("import ast, sys")
explorer.has_import("from math import factorial as f")
```

#### `has_class()`

```python
code_str = """
class spam:
  pass
"""

Node(code_str).has_class("spam")
```

### Misc

#### `is_equivalent()`

Compares normalized source produced by parsing and unparsing the node and the target string. This ignores comments and formatting differences, but does not establish that two programs behave the same. For example, `x = 1 + 1` does not match `x = 2`. The target must be parseable on its own, so standalone `except` and `case` clauses cannot be used as target strings.

```python
Node("x = 1").is_equivalent("x = 1") # True
Node("\nx = 1").is_equivalent("x =    1") # True
Node("x = 1").is_equivalent("x = 2") # False
```

#### `is_empty()`

This is syntactic sugar for `== Node()`.

```python
Node().is_empty() # True
Node("x = 1").find_variable("x").is_empty() # False
```

#### `value_is_call()`

This allows you to check if the return value of a function call is assigned to a variable.

```python
explorer = Node("def foo():\n  x = bar()")

explorer.find_function("foo").find_variable("x").value_is_call("bar") # True
```

#### `is_integer()`

```python
Node("x = 1").find_variable("x").is_integer() # True
Node("x = '1'").find_variable("x").is_integer() # False
```

#### `inherits_from()`

```python
Node("class C(A, B):\n  pass").find_class("C").inherits_from("A") # True
Node("class C(A, B):\n  pass").find_class("C").inherits_from("A", "B") # True
```

#### `is_ordered()`

Returns a boolean indicating if the statements passed as arguments are found in the same order in the tree (statements can be non-consecutive).

```python
code_str = """
x = 1
if x:
  print("x is:")
  y = 0
  print(x)
  return y
x = 0
"""

if_str = """
if x:
  print("x is:")
  y = 0
  print(x)
  return y
"""
explorer = Node(code_str)
explorer.is_ordered("x=1", "x=0") # True
explorer.is_ordered("x=1", if_str, "x=0") # True
explorer.find_ifs()[0].is_ordered("print('x is:')", "print(x)", "return y") # True
explorer.is_ordered("x=0", "x=1") # False
explorer.find_ifs()[0].is_ordered("print(x)", "print('x is:')") # False
```

#### How to get the nth statement

```python
stmts = """
if True:
  pass

x = 1
"""

Node(stmts)[1].is_equivalent("x = 1") # True
```

### Regex-based Helpers

Prefer the [Python AST-based helpers](#python-ast-based-helpers) for code structure. The following JavaScript utilities process Python source as text.

#### Removing Python Comments

Use `__helpers.python.removeComments(code)` to remove Python comments from code strings:

```js
const codeWithComments = `
def test():  # This is a comment
    return True  # Another comment
`;

const cleaned = __helpers.python.removeComments(codeWithComments);
// Returns: "\ndef test():  \n    return True  \n"
```

This regex-based utility is not a Python tokenizer. It also removes JavaScript-style `//` and `/* */` text, so it can corrupt valid Python: `value = 10 // 2` becomes `value = 10 `, and a `#` inside a string starts removal too. Prefer AST helpers for structural checks; they ignore comments while preserving strings and operators.

#### `getDef(code, functionName)`

Available via `__helpers.python.getDef(code, functionName)`, this function extracts function definition details from Python code:

```js
const pythonCode = `
def calculate_area(length, width):
    """Calculate rectangle area"""
    return length * width
`;

// Via __helpers.python.getDef
const funcInfo = __helpers.python.getDef(pythonCode, 'calculate_area');
// Returns: {
//   def: "def calculate_area(length, width):\n    \"\"\"Calculate rectangle area\"\"\"\n    return length * width\n",
//   function_parameters: "length, width",
//   function_body: "    \"\"\"Calculate rectangle area\"\"\"\n    return length * width\n",
//   function_indentation: 0
// }
```

This returns the first regex match, or `null` when no match exists. The regex does not parse Python function syntax: return annotations and parentheses inside default values can prevent a match. Whitespace, including the trailing newline in this example, is retained. Prefer `find_function`, `find_async_function`, and `find_body` when inspecting a definition.

#### `getBlock(code, blockPattern)`

Available via `__helpers.python.getBlock(code, blockPattern)`, this function extracts an indented block by matching text immediately before its colon against a string or regex. Strings are escaped and matched literally; regex inputs contribute their source pattern. Include the keyword and condition or loop expression to match the intended header. For example:

```js
const pythonCode = `
if x > 0:
    print("positive")
else:
    print("not positive")

for i in range(5):
    print(i)
`;

// Extract if block
const ifBlock = __helpers.python.getBlock(pythonCode, 'if x > 0');
// {
//   block_body: '    print("positive")',
//   block_condition: 'if x > 0',
//   block_indentation: 0
// }

// Extract for block
const forBlock = __helpers.python.getBlock(pythonCode, 'for i in range(5)');
// {
//   block_body: '    print(i)\n',
//   block_condition: 'for i in range(5)',
//   block_indentation: 0
// }
```

Passing only `'if'` or `'for'` returns `null` for this example because the condition or loop expression is missing from the pattern. The helper returns only the matched block body, not adjacent `elif` or `else` branches. It relies on spaces for indentation and does not parse Python structure. Regex flags from `blockPattern` are discarded; matching uses the helper's `s` and `m` flags. Prefer `find_if`, `find_for`, `find_while`, and their body helpers for structural checks.

### Notes on Python

- Python does **not** allow newline characters between keywords and their arguments. E.g:

```python
def
    add(x, y):
    return x + y
```

- Python **does** allow newline characters between function arguments. E.g:

```python
def add(
  x,
  y
):
    return x + y
```

Source: https://contribute.freecodecamp.org/curriculum-help/index.mdx
