Use the Curriculum Helpers
The test runner exposes helpers through __helpers for testing campers’ code.
General Helpers
Section titled “General Helpers”Removing Whitespace
Section titled “Removing Whitespace”Use __helpers.removeWhiteSpace(codeString) to remove all whitespace from code:
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
Section titled “retryingTest”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:
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
Section titled “Regex Utilities”These helpers build text patterns. Prefer the language-specific helpers below when testing code structure.
.permutateRegex()
Section titled “.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 for literal text containing regex metacharacters.
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; // trueThe method supports named capturing groups and backreferences:
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"'); // trueregex.test("'b' === a"); // trueregex.test('a === `b`'); // trueregex.test(`a === 'b"`); // falseOptions
Section titled “Options”capture: boolean- Whole regex is wrapped in regex group. Ifcaptureistruethe group will be capturing, otherwise it will be non-capturing. Defaults tofalse.elementsSeparator: string- Separates permuted elements within single permutation. Defaults to\s*\|\|\s*.permutationsSeparator: string- Separates permutations. Defaults to|.
__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__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__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()
Section titled “.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.
const regex1 = /a\s/;const regex2 = /b/;__helpers.concatRegex(regex1, regex2).source === 'a\\sb'; // true.escapeRegExp()
Section titled “.escapeRegExp()”Available via __helpers.escapeRegExp(string), this method escapes special regular expression characters in a string.
__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
Section titled “HTML Helper”Removing HTML Comments
Section titled “Removing HTML Comments”Use __helpers.removeHtmlComments(codeString) to remove HTML comments (<!-- -->) from code:
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
Section titled “CSS Helper”CSS Helper Class
Section titled “CSS Helper Class”To instantiate the helper within a test block, use this:
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
Section titled “Methods”There are a few methods available for parsing and testing CSS.
.getStyle()
Section titled “.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:
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().
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:
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()
Section titled “.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:
.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():
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()
Section titled “.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:
@media (max-width: 100px) { body { background-color: green; }}Find the media rule by its condition, then inspect its nested rules:
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()
Section titled “.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
Section titled “Less Frequent Methods”These methods are not as commonly used, but are available if needed.
.getStyleDeclarations()
Section titled “.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()
Section titled “.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()
Section titled “.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()
Section titled “.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()
Section titled “.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
Section titled “Removing CSS Comments”Use __helpers.removeCssComments(codeString) to remove CSS-style comments (/* */) from code:
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
Section titled “JS Helper”The Explorer Class
Section titled “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, 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()toString()matches()variablesvalueobjectPropsfunctionsandallFunctionsparametershasReturn()classesmethodsclassConstructor,classProps, andconstructorPropsdoesExtend()
Mockers
Section titled “Mockers”These mock methods that can prove to be unpredictable for testing purposes.
RandomMocker
Section titled “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.
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.
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.
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:
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:
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
Section titled “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.
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
Section titled “Regex-based Helpers”These helpers match text rather than parse JavaScript structure. For AST-based alternatives, see the Explorer class.
Removing JavaScript Comments
Section titled “Removing JavaScript Comments”Use __helpers.removeJSComments(codeString) to remove JavaScript comments (// and /* */) from code:
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()
Section titled “.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.
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 ();'); // falseUse 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()
Section titled “.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 to inspect functions. This regex helper does not parse nested blocks, string literals, or template literals.
const regex = __helpers.functionRegex('foo', ['bar', 'baz']);regex.test('function foo(bar, baz){}'); // trueregex.test('function foo(bar, baz, qux){}'); // falseregex.test('foo = (bar, baz) => {}'); // trueOptions:
capture: boolean- Iftrue, the regex will capture the function definition, including its body, otherwise not. Defaults tofalse.includeBody: boolean- Iftrue, the regex will include the function body in the match. Otherwise it will stop at the first bracket. Defaults totrue.
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 capturedconst 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:
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.
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")'); // truematch[1].includes('age.trim'); // falseUse Explorer to find the function and inspect its structure instead. This example checks a return containing template interpolation after a nested block:
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()
Section titled “.getFunctionParams()”Available via __helpers.getFunctionParams(codeString), this method extracts parameter information from a function definition string.
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 and inspect each parameter’s value when checking defaults.
TypeScript Helpers
Section titled “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
Section titled “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:
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:
class Spam { get42(): number { return 42; }}To check the return type annotation you would write:
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
Section titled “Methods and Properties”isEmpty()
Section titled “isEmpty()”Returns true if the Explorer instance has no AST node, otherwise returns false.
const explorer = await __helpers.Explorer('const a = 1;');explorer.isEmpty(); // falsetoString()
Section titled “toString()”Returns the source code representation of the current node, or "no ast" if the node is empty.
const explorer = await __helpers.Explorer('const a = 1;');explorer.toString(); // "const a = 1;"matches(other: string | Explorer)
Section titled “matches(other: string | Explorer)”Compares the current tree with another tree or string, ignoring semicolons, comments, and irrelevant whitespace differences.
const explorer = await __helpers.Explorer('const a = 1;');explorer.matches('const a = 1'); // true (ignores whitespace and semicolons)explorer.matches('const b = 1;'); // falseEverything 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:
const explorer = await __helpers.Explorer('const f = (x) => x;');explorer.matches('const f = (x) => x'); // trueexplorer.matches('const f = x => x'); // false
const another = await __helpers.Explorer('const a = (1);');another.matches('const a = 1'); // falseString 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:
const explorer = await __helpers.Explorer("const a = 'hello';");explorer.matches('const a = "hello"'); // trueexplorer.matches('const a = `hello`'); // falseBecause 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:
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
Section titled “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.
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;'); // trueb.matches('let b = 2;'); // truec.matches('const c = () => {}'); // trueReturns an Explorer instance representing the assigned value of a variable, property, parameter, or property assignment. Returns an empty Explorer if there is no initializer.
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 }'); // truec.value.toString(); // "a + 1"objectProps
Section titled “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.
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
Section titled “functions and allFunctions”functions returns function declarations only. allFunctions also includes arrow functions and function expressions assigned to variables.
const explorer = await __helpers.Explorer( 'function foo() { return 42; } const bar = () => 24;');explorer.functions; // { foo: Explorer }explorer.allFunctions; // { foo: Explorer, bar: Explorer }parameters
Section titled “parameters”Returns an array of Explorer instances representing the parameters of a function or method.
const explorer = await __helpers.Explorer( 'function foo(x: number, y: string) { return 42; }');const parameters = explorer.functions.foo.parameters;parameters[0].matches('x: number'); // trueparameters[1].matches('y: string'); // trueannotation
Section titled “annotation”Returns an Explorer instance representing the type annotation of the current node, or an empty Explorer if none exists.
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)
Section titled “hasAnnotation(annotation: string)”Returns true if the current node has a type annotation matching the specified string.
const explorer = await __helpers.Explorer('const a: number = 1;');const { a } = explorer.variables;a.hasAnnotation('number'); // truea.hasAnnotation('string'); // falsehasReturnAnnotation(annotation: string)
Section titled “hasReturnAnnotation(annotation: string)”Returns true if the function/method has a return type annotation matching the specified type.
const explorer = await __helpers.Explorer( 'function foo(): number { return 42; }');const { foo } = explorer.functions;foo.hasReturnAnnotation('number'); // truefoo.hasReturnAnnotation('string'); // falsehasReturn(value: string)
Section titled “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.
const explorer = await __helpers.Explorer('function foo() { return 42; }');const { foo } = explorer.functions;foo.hasReturn('42'); // trueReturns an object mapping type alias names to Explorer instances for all type aliases in the current scope.
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; };'); // truetypes.Bar.matches('type Bar = { y: string; };'); // trueinterfaces
Section titled “interfaces”Returns an object mapping interface names to Explorer instances for all interfaces in the current scope.
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; }'); // trueinterfaces.Bar.matches('interface Bar { y: string; }'); // trueclasses
Section titled “classes”Returns an object mapping class names to Explorer instances for all classes in the current scope.
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; }'); // trueclasses.Bar.matches('class Bar { y: string; }'); // truemethods
Section titled “methods”Returns an object mapping method names to Explorer instances for all methods in the current class.
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
Section titled “classConstructor, classProps, and constructorProps”Use these to inspect class constructors and class-related properties.
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; }'); // trueFoo.classProps; // { prop1: Explorer }Foo.constructorProps; // { y: Explorer }typeProps
Section titled “typeProps”Returns an object mapping property names to Explorer instances for all properties in a type, interface, or type literal.
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[])
Section titled “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 }.
const explorer = await __helpers.Explorer( 'type Foo = { x: number; y: string; z?: boolean; };');const { Foo } = explorer.types;Foo.hasTypeProps({ name: 'x' }); // trueFoo.hasTypeProps([ { name: 'x', type: 'number' }, { name: 'y', type: 'string', isOptional: false }, { name: 'z', isOptional: true }]); // trueFoo.hasTypeProps({ name: 'a' }); // falseisUnionOf(types: string[])
Section titled “isUnionOf(types: string[])”Returns true if the current node has a union type annotation whose members exactly match the specified types, ignoring order.
const explorer = await __helpers.Explorer( 'const a: number | string | boolean;');const { a } = explorer.variables;a.isUnionOf(['number', 'string', 'boolean']); // truea.isUnionOf(['number', 'string']); // falsehasCast(expectedType?: string)
Section titled “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.
const explorer = await __helpers.Explorer( 'const a = 1 as number; const b = 2 as string;');const { a, b } = explorer.variables;a.value.hasCast(); // truea.value.hasCast('number'); // truea.value.hasCast('string'); // falseb.value.hasCast('string'); // truehasNonNullAssertion()
Section titled “hasNonNullAssertion()”Checks whether the current expression is a non-null assertion (!).
const explorer = await __helpers.Explorer('const a = maybeValue!;');const { a } = explorer.variables;a.value.hasNonNullAssertion(); // truedoesExtend(basesToCheck: string | string[])
Section titled “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.
const explorer = await __helpers.Explorer('class Dog extends Animal {}');const { Dog } = explorer.classes;Dog.doesExtend('Animal'); // trueDog.doesExtend('Cat'); // falsedoesImplement(basesToCheck: string | string[])
Section titled “doesImplement(basesToCheck: string | string[])”Checks if a class implements the specified interface(s). Accepts a single string or an array of strings.
const explorer = await __helpers.Explorer('class Foo implements Bar, Baz { }');const { Foo } = explorer.classes;Foo.doesImplement('Bar'); // trueFoo.doesImplement(['Bar', 'Baz']); // trueFoo.doesImplement('Spam'); // falsetypeParameters and typeArguments
Section titled “typeParameters and typeArguments”typeParameters returns generic declarations (like <T>), and typeArguments returns generic usages (like <string>).
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'); // trueAccess modifiers
Section titled “Access modifiers”Use isPrivate(), isProtected(), isPublic(), and isReadOnly() on class members and type properties.
const explorer = await __helpers.Explorer( 'class Foo { private readonly x: number; }');const { Foo } = explorer.classes;const { x } = Foo.classProps;x.isPrivate(); // truex.isReadOnly(); // truePython Helper
Section titled “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
Section titled “Python AST-based Helpers”These helpers parse Python source into an AST without executing it.
Basic Usage Pattern
Section titled “Basic Usage Pattern”For Python challenges, these helpers need to be run in Python and tests should be formatted as:
({ test: () => assert(runPython(`<python code>`)) });For complex tests, you can use assert inside Python and avoid the assert around runPython.
({ test: () => runPython(`<python code>`) });An example would be if you need to test multiple things at once.
({ 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:
class Spam: def __str__(self): return 'spam!'To check the return value of __str__ you would write:
({ 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:
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
Section titled “Methods”Formatting output
Section titled “Formatting output”str returns a string that would parse to the same AST as the node. For example:
function_str = """def foo(): # will not be in the output x = 1
"""output_str = """def foo(): x = 1"""str(Node(function_str)) == output_str # TrueThe 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
Section titled “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()
Section titled “find_function()”Node('def foo():\n x = "1"').find_function("foo").has_variable("x") # Truefind_functions()
Section titled “find_functions()”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")) # 2explorer.find_class("Spam").find_functions("foo")[0].is_equivalent("@property\n@abstractmethod\ndef foo(self):\n return self.x") # Trueexplorer.find_class("Spam").find_functions("foo")[1].is_equivalent("@foo.setter\n@abstractmethod\ndef foo(self, new_x):\n self.x = new_x") # Truefind_async_function()
Section titled “find_async_function()”Node('async def foo():\n await bar()').find_async_function("foo").is_equivalent("async def foo():\n await bar()") # Truefind_awaits()
Section titled “find_awaits()”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()") # Trueexplorer.find_async_function("foo").find_awaits()[1].is_equivalent("await func()") # Trueexplorer.find_async_function("foo").find_ifs()[0].find_awaits()[0].is_equivalent("await spam()") # Truefind_variable()
Section titled “find_variable()”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()
Section titled “find_variables()”Returns a list of all variable assignments with the given name (unlike find_variable which returns only the first match).
code_str = """x: int = 0a.b = 0x = 5a.b = 2x = 10"""node = Node(code_str)len(node.find_variables("x")) # 3node.find_variables("x")[0].is_equivalent("x: int = 0") # Truenode.find_variables("x")[1].is_equivalent("x = 5") # Truenode.find_variables("x")[2].is_equivalent("x = 10") # Truelen(node.find_variables("a.b")) # 2node.find_variables("a.b")[0].is_equivalent("a.b = 0") # Truenode.find_variables("a.b")[1].is_equivalent("a.b = 2") # Truefind_aug_variable()
Section titled “find_aug_variable()”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)):
Node('def foo():\n x = "1"').find_variable("x") == Node() # Truefind_body()
Section titled “find_body()”func_str = """def foo(): x = 1"""Node(func_str).find_function("foo").find_body().is_equivalent("x = 1") # Truefind_return()
Section titled “find_return()”code_str = """def foo(): if x == 1: return False return True"""Node(code_str).find_function("foo").find_return().is_equivalent("return True") # TrueNode(code_str).find_function("foo").find_ifs()[0].find_return().is_equivalent("return False") # Truefind_calls()
Section titled “find_calls()”code_str = """print(1)print(2)foo("spam")obj.foo("spam")obj.bar.foo("spam")"""explorer = Node(code_str)len(explorer.find_calls("print")) # 2explorer.find_calls("print")[0].is_equivalent("print(1)")explorer.find_calls("print")[1].is_equivalent("print(2)")len(explorer.find_calls("foo")) # 3explorer.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()
Section titled “find_call_args()”explorer = Node("print(1, 2)")len(explorer.find_calls("print")[0].find_call_args()) # 2explorer.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()
Section titled “find_class()”class_str = """class Foo: def __init__(self): pass"""Node(class_str).find_class("Foo").has_function("__init__") # Truefind_ifs()
Section titled “find_ifs()”if_str = """if x == 1: x += 1elif x == 2: passelse: 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()
Section titled “find_if()”if_str = """if x == 1: x += 1elif x == 2: passelse: 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()
Section titled “find_whiles()”while_str = """while True: x += 1else: return
while False: pass"""explorer = Node(while_str)explorer.find_whiles()[0].is_equivalent("while True:\n x += 1\nelse:\n return") # Trueexplorer.find_whiles()[1].is_equivalent("while False:\n pass") # Truefind_while()
Section titled “find_while()”while_str = """while True: x += 1else: return
while False: pass"""explorer = Node(while_str)explorer.find_while("True").is_equivalent("while True:\n x += 1\nelse:\n return") # Trueexplorer.find_while("False").is_equivalent("while False:\n pass") # Truefind_conditions()
Section titled “find_conditions()”if_str = """if x > 0: x = 1elif x < 0: x = -1else: return x"""explorer1 = Node(if_str)len(explorer1.find_ifs()[0].find_conditions()) # 3explorer1.find_ifs()[0].find_conditions()[0].is_equivalent("x > 0") # Trueexplorer1.find_ifs()[0].find_conditions()[1].is_equivalent("x < 0") # Trueexplorer1.find_ifs()[0].find_conditions()[2] == Node() # TrueNode("x = 1").find_conditions() # []
while_str = """while True: x += 1else: return
while False: pass"""explorer2 = Node(while_str)explorer2.find_whiles()[0].find_conditions()[0].is_equivalent("True") # Trueexplorer2.find_whiles()[0].find_conditions()[1] == Node() # Trueexplorer2.find_whiles()[1].find_conditions()[0].is_equivalent("False") # Truefind_for_loops()
Section titled “find_for_loops()”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") # Trueexplorer.find_for_loops()[1].is_equivalent("for i in range(4):\n pass") # Truefind_for()
Section titled “find_for()”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") # Trueexplorer.find_for("i", "range(4)").is_equivalent("for i in range(4):\n pass") # Truefind_for_vars()
Section titled “find_for_vars()”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)") # Trueexplorer.find_for_loops()[1].find_for_vars().is_equivalent("i") # Truefind_for_iter()
Section titled “find_for_iter()”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)") # Trueexplorer.find_for_loops()[1].find_for_iter().is_equivalent("range(4)") # Truefind_bodies()
Section titled “find_bodies()”if_str = """if True: x = 1elif False: x = 2"""explorer1 = Node(if_str)explorer1.find_ifs()[0].find_bodies()[0].is_equivalent("x = 1") # Trueexplorer1.find_ifs()[0].find_bodies()[1].is_equivalent("x = 2") # True
while_str = """while True: x += 1else: x = 0
while False: pass"""explorer2 = Node(while_str)explorer2.find_whiles()[0].find_bodies()[0].is_equivalent("x += 1") # Trueexplorer2.find_whiles()[0].find_bodies()[1].is_equivalent("x = 0") # Trueexplorer2.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)") # Trueexplorer3.find_for_loops()[0].find_bodies()[1].is_equivalent("print(x)") # Trueexplorer3.find_for_loops()[1].find_bodies()[0].is_equivalent("pass") # Truefind_imports()
Section titled “find_imports()”code_str = """import ast, sysfrom math import factorial as f"""
explorer = Node(code_str)len(explorer.find_imports()) # 2explorer.find_imports()[0].is_equivalent("import ast, sys")explorer.find_imports()[1].is_equivalent("from math import factorial as f")find_comps()
Section titled “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.
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()) # 4explorer.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()
Section titled “find_comp_iters()”Returns a list of comprehension/generator expression iterables. It can be chained to find_variable, find_return, find_call_args()[n].
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()) # 1explorer.find_variable("x").find_comp_iters()[0].is_equivalent("lst")
len(explorer.find_function("foo").find_return().find_comp_iters()) # 2explorer.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()
Section titled “find_comp_targets()”Returns a list of comprehension/generator expression targets (i.e. the iteration variables).
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()) # 1explorer.find_variable("x").find_comp_targets()[0].is_equivalent("i")
len(explorer.find_function("foo").find_return().find_comp_targets()) # 2explorer.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()
Section titled “find_comp_key()”Returns the dictionary comprehension key.
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()
Section titled “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.
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()
Section titled “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.
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()) # 2explorer.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()
Section titled “find_trys()”Returns a list of all try statements.
code_str = """try: x = 1 / 0except 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()) # 2node.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')") # Truenode.find_trys()[1].is_equivalent("try:\n y = int('abc')\nexcept:\n pass") # Truefind_excepts()
Section titled “find_excepts()”Returns a list of all except handlers in a try statement.
code_str = """try: x = 1 / 0except 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()) # 3is_equivalent() cannot be used on the items found by find_excepts() because a standalone except raises a SyntaxError.
find_except()
Section titled “find_except()”Returns a specific except handler matching the exception type and optional variable name.
code_str = """try: x = 1 / 0except 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')") # Truetry_stmt.find_except("ValueError", "e").find_body().is_equivalent("print(f'value error: {e}')") # Truetry_stmt.find_except().find_body().is_equivalent("print('other error')") # True (bare except)has_except()
Section titled “has_except()”Returns True if a try statement has an except handler for the given exception type and variable name.
code_str = """try: x = 1 / 0except 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") # Truetry_stmt.has_except("ValueError", "e") # Truetry_stmt.has_except("ValueError") # Falsefind_try_else()
Section titled “find_try_else()”Returns the else block of a try statement.
code_str = """try: x = 1except 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") # Truefind_finally()
Section titled “find_finally()”Returns the finally block of a try statement.
code_str = """try: x = 1except 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") # Truefind_matches()
Section titled “find_matches()”Returns a list of match statements.
code_str = """match x: case 0: pass case _: passmatch y: case 1: pass"""node = Node(code_str)len(node.find_matches()) # 2node.find_matches()[0].is_equivalent("match x:\n case 0:\n pass\n case _:\n pass") # Truefind_match_subject()
Section titled “find_match_subject()”Returns the subject of a match statement.
code_str = """match x: case 0: pass"""node = Node(code_str)node.find_matches()[0].find_match_subject().is_equivalent("x") # Truefind_match_cases()
Section titled “find_match_cases()”Returns a list of case blocks in a match statement.
code_str = """match x: case 0: print(0) print('spam') case _: pass"""node = Node(code_str)len(node.find_matches()[0].find_match_cases()) # 2node.find_matches()[0].find_match_cases()[0].find_body().is_equivalent("print(0)\nprint('spam')") # Truenode.find_matches()[0].find_match_cases()[1].find_body().is_equivalent("pass") # Trueis_equivalent() cannot be used on the items found by find_match_cases() because a standalone case raises a SyntaxError.
find_case_pattern()
Section titled “find_case_pattern()”Returns the pattern of a case block.
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") # Truenode.find_matches()[0].find_match_cases()[1].find_case_pattern().is_equivalent("[a, b]") # Truenode.find_matches()[0].find_match_cases()[2].find_case_pattern().is_equivalent("_") # Truefind_case_guard()
Section titled “find_case_guard()”Returns the guard condition of a case block.
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") # Truenode.find_matches()[0].find_match_cases()[1].find_case_guard().is_equivalent("y == -1") # Truenode.find_matches()[0].find_match_cases()[2].find_case_guard().is_empty() # True (no guard)Getting values
Section titled “Getting values”get_ functions return the value of the node, not the node itself.
get_variable()
Section titled “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.
Node("x = 1").get_variable("x") # 1The 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
Section titled “Checking for existence”has_ functions return a boolean indicating whether the node exists.
has_variable()
Section titled “has_variable()”Node("x = 1").has_variable("x") # Truehas_function()
Section titled “has_function()”Node("def foo():\n pass").has_function("foo") # Truehas_stmt()
Section titled “has_stmt()”Returns a boolean indicating if the specified statement is found.
Node("name = input('hi')\nself.matrix[1][5] = 3").has_stmt("self.matrix[1][5] = 3") # Truehas_args()
Section titled “has_args()”Node("def foo(*, a, b, c=0):\n pass").find_function("foo").has_args("*, a, b, c=0") # Truehas_pass()
Section titled “has_pass()”Node("def foo():\n pass").find_function("foo").has_pass() # TrueNode("if x==1:\n x+=1\nelse: pass").find_ifs()[0].find_bodies()[1].has_pass() # Truehas_return()
Section titled “has_return()”Returns a boolean indicating if the function returns the specified expression/object.
code_str = """def foo(): if x == 1: return False return True"""explorer = Node(code_str)explorer.find_function("foo").has_return("True") # Trueexplorer.find_function("foo").find_ifs()[0].has_return("False") # Truehas_returns()
Section titled “has_returns()”Returns a boolean indicating if the function has the specified return annotation.
Node("def foo() -> int:\n return 0").find_function("foo").has_returns("int") # TrueNode("def foo() -> 'spam':\n pass").find_function("foo").has_returns("'spam'") # Truehas_decorators()
Section titled “has_decorators()”code_str = """class A: @property @staticmethod def foo(): pass"""Node(code_str).find_class("A").find_function("foo").has_decorators("property") # TrueNode(code_str).find_class("A").find_function("foo").has_decorators("property", "staticmethod") # TrueNode(code_str).find_class("A").find_function("foo").has_decorators("staticmethod", "property") # False, order does matterhas_call()
Section titled “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.
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()
Section titled “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.
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") # Truenode.block_has_call("sorted") # Truenode.block_has_call("sort", "spam") # Truenode.block_has_call("get", "eggs") # Truenode.block_has_call("get") # Truenode.block_has_call("split") # FalseAsserting absence of a function call
Section titled “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:
Node("print(123)").has_call("print()") # FalseNode("print(123)").block_has_call("print") # TrueNode("saved = print").block_has_call("print") # Truehas_import()
Section titled “has_import()”code_str = """import ast, sysfrom 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()
Section titled “has_class()”code_str = """class spam: pass"""
Node(code_str).has_class("spam")is_equivalent()
Section titled “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.
Node("x = 1").is_equivalent("x = 1") # TrueNode("\nx = 1").is_equivalent("x = 1") # TrueNode("x = 1").is_equivalent("x = 2") # Falseis_empty()
Section titled “is_empty()”This is syntactic sugar for == Node().
Node().is_empty() # TrueNode("x = 1").find_variable("x").is_empty() # Falsevalue_is_call()
Section titled “value_is_call()”This allows you to check if the return value of a function call is assigned to a variable.
explorer = Node("def foo():\n x = bar()")
explorer.find_function("foo").find_variable("x").value_is_call("bar") # Trueis_integer()
Section titled “is_integer()”Node("x = 1").find_variable("x").is_integer() # TrueNode("x = '1'").find_variable("x").is_integer() # Falseinherits_from()
Section titled “inherits_from()”Node("class C(A, B):\n pass").find_class("C").inherits_from("A") # TrueNode("class C(A, B):\n pass").find_class("C").inherits_from("A", "B") # Trueis_ordered()
Section titled “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).
code_str = """x = 1if x: print("x is:") y = 0 print(x) return yx = 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") # Trueexplorer.is_ordered("x=1", if_str, "x=0") # Trueexplorer.find_ifs()[0].is_ordered("print('x is:')", "print(x)", "return y") # Trueexplorer.is_ordered("x=0", "x=1") # Falseexplorer.find_ifs()[0].is_ordered("print(x)", "print('x is:')") # FalseHow to get the nth statement
Section titled “How to get the nth statement”stmts = """if True: pass
x = 1"""
Node(stmts)[1].is_equivalent("x = 1") # TrueRegex-based Helpers
Section titled “Regex-based Helpers”Prefer the Python AST-based helpers for code structure. The following JavaScript utilities process Python source as text.
Removing Python Comments
Section titled “Removing Python Comments”Use __helpers.python.removeComments(code) to remove Python comments from code strings:
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)
Section titled “getDef(code, functionName)”Available via __helpers.python.getDef(code, functionName), this function extracts function definition details from Python code:
const pythonCode = `def calculate_area(length, width): """Calculate rectangle area""" return length * width`;
// Via __helpers.python.getDefconst 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)
Section titled “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:
const pythonCode = `if x > 0: print("positive")else: print("not positive")
for i in range(5): print(i)`;
// Extract if blockconst ifBlock = __helpers.python.getBlock(pythonCode, 'if x > 0');// {// block_body: ' print("positive")',// block_condition: 'if x > 0',// block_indentation: 0// }
// Extract for blockconst 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
Section titled “Notes on Python”- Python does not allow newline characters between keywords and their arguments. E.g:
def add(x, y): return x + y- Python does allow newline characters between function arguments. E.g:
def add( x, y): return x + y