Use the Curriculum Helpers
The test runner has access to a few helpers that can assist with 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;}".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.
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(...regexOrStrings), this method combines and returns a compiled regex when given two or more regexes or strings.
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'; // trueHTML 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>"CSS Helper
Section titled “CSS Helper”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; }"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.
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 a CSS style declaration object.
For example, if the camper has written the following CSS:
body { background: linear-gradient(45deg, rgb(118, 201, 255), rgb(247, 255, 222)); margin: 0; padding: 0; width: 100%; height: 100vh; overflow: hidden;}If you call the getStyle method with body as an argument, you would get an object that looks like this:
{ 0: "background-image", 1: "background-position-x", 2: "background-position-y", 3: "background-size", 4: "background-repeat-x", 5: "background-repeat-y", 6: "background-attachment", 7: "background-origin", 8: "background-clip", 9: "background-color", 10: "margin-top", 11: "margin-right", 12: "margin-bottom", 13: "margin-left", 14: "padding-top", 15: "padding-right", 16: "padding-bottom", 17: "padding-left", 18: "width", 19: "height", 20: "overflow-x", 21: "overflow-y", "accentColor": "", "additiveSymbols": "", "alignContent": "", "alignItems": "", ...}This method allows you to test that specific properties have been set:
assert.strictEqual(helper.getStyle('body')?.width, '100%');The helper attaches a .getPropVal() method to the style declaration object that allows you to get the value of a specific property:
assert.strictEqual(helper.getStyle('body').getPropVal('width'), '100%');On the style object returned, there is a native method called getPropertyValue that can retrieve
CSS properties. More info at MDN Docs.
.getStyleAny()
Section titled “.getStyleAny()”The .getStyleAny() method takes multiple CSS possible selectors and returns
the first matching CSS style declaration object.
For example, if the camper has written the following CSS:
.sky { background: radial-gradient( closest-corner circle at 15% 15%, #ffcf33, #ffcf33 20%, #ffff66 21%, #bbeeff 100% );}If you call the getStyleAny() method with .earth and .sky as arguments, you would get an object that looks like this:
{ 0: "background-image", 1: "background-position-x", 2: "background-position-y", 3: "background-size", 4: "background-repeat-x", 5: "background-repeat-y", 6: "background-attachment", 7: "background-origin", 8: "background-clip", 9: "background-color", 10: "margin-top", 11: "margin-right", 12: "margin-bottom", 13: "margin-left", 14: "padding-top", 15: "padding-right", 16: "padding-bottom", 17: "padding-left", 18: "width", 19: "height", 20: "overflow-x", 21: "overflow-y", "accentColor": "", "additiveSymbols": "", "alignContent": "", "alignItems": "", ...}This method allows you to test that specific properties have been set:
assert.strictEqual(helper.getStyleAny(['.earth', '.sky'])?.width, '100%');The helper attaches a .getPropVal() method to the style declaration object that allows you to get the value of a specific property:
assert.strictEqual( helper.getStyleAny(['.earth', '.sky']).getPropVal('width'), '100%');.getCSSRules()
Section titled “.getCSSRules()”The .getCSSRules() takes an at-rule type from the union media | fontface | import | keyframes, and returns an array of CSS rules matching that at-rule.
For example, if the camper has written the following code:
@media (max-width: 100px) { body { background-color: green; }}Then the returned value of helper.getCSSRules('media') would be this array:
[ { conditionText: "(max-width: 100px)", cssRules: [ selectorText: 'body', style: CSSStyleDeclaration, styleMap: StylePropertyMap, cssRules: CSSRuleList, type: 1, ... ], cssText: "@media (max-width: 100px) {\n body { background-color: green; }\n}", ... }]You can then test that the camper has written the correct media query:
const hasCorrectHeight = helper .getCSSRules('media') .some(x => x.style.height === '3px');assert.isTrue(hasCorrectHeight);.getRuleListsWithinMedia()
Section titled “.getRuleListsWithinMedia()”The .getRuleListsWithinMedia() method takes a media text (e.g. ("max-width: 200")) and returns the CSS rules within that media query.
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 CSS style declaration objects (from the .getStyle() method).
.isPropertyUsed()
Section titled “.isPropertyUsed()”The .isPropertyUsed() method takes a CSS property and checks if it has been set/used anywhere in the camper’s CSS.
.getStyleRule()
Section titled “.getStyleRule()”The .getStyleRule() method takes a CSS selector and returns the CSS Style Declaration, much like .getStyle(). However, the declaration returned from this method includes an additional .isDeclaredAfter() method which takes a selector and returns whether this rule is declared after the selector passed in.
.getStyleSheet()
Section titled “.getStyleSheet()”The .getStyleSheet() method returns the camper’s CSS, parsed into a CSS Style Sheet object.
.selectorsFromSelector()
Section titled “.selectorsFromSelector()”The .selectorsFromSelector method returns all equivalent selectors from a given selector based on what’s nearby
the given element.
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.
JS Helper
Section titled “JS Helper”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'); "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 native Math.random function.
const randomMocker = new __helpers.RandomMocker();randomMocker.mock();Math.random(); // first call is always 0.2523451747838408Math.random(); // second call is always 0.08812504541128874randomMocker.mock();Math.random(); // generator is reset, so we get 0.2523451747838408 againrandomMocker.restore();Math.random(); // back to native Math.randomAvailable 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.
To restore the original method, call restore() on the spy object.
const obj = { method: (arg1, arg2) => `called by: ${arg1} + ${arg2}` };const spy = __helpers.spyOn(obj, 'method');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"]// to turn it offspy.restore();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');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, the spy’s restore method restores the original method.
Static Methods
Section titled “Static Methods”There are a few methods on the __helpers object to retrieve data from JavaScript methods.
These do NOT need to be instantiated; they are static methods.
.isCalledWithNoArgs()
Section titled “.isCalledWithNoArgs()”Available via __helpers.isCalledWithNoArgs(functionName, codeString), this method checks the current code string to see if a function is called with any arguments.
If the function is called without arguments, true is returned. Otherwise it returns false. Additionally, if the function call is commented out, false is returned.
const code = 'console.log(); alert("test");';__helpers.isCalledWithNoArgs('console.log', code); // true__helpers.isCalledWithNoArgs('alert', code); // false.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.
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"; /, regEx);
const match = `var x = "y";function foo(bar, baz){}`.match(regex);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[1]; // "function foo(bar, baz){"NOTE: capture does not work properly with arrow functions. It will capture text after the function body, too.
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".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 }// ].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\\]"Python Helper
Section titled “Python Helper”The __helpers object includes comprehensive Python code analysis tools through the python property.
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"Python AST-based Helpers
Section titled “Python AST-based Helpers”These helpers provide powerful Abstract Syntax Tree (AST) analysis capabilities for Python challenges.
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!'"))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”find_ functions search the current scope and 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
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()”Node("x = 1").get_variable("x") # 1Checking 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()”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()”Checks if a function/method call exists within a specific function or the entire code.
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") # Falsehas_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()”This is a somewhat loose check. The AST of the target string and the AST of the node do not have to be identical, but the code must be equivalent.
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") # TruePython Helper Functions
Section titled “Python Helper Functions”The Python helpers also include some standalone utility functions for extracting specific information from Python code.
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",// function_parameters: "length, width",// function_body: " \"\"\"Calculate rectangle area\"\"\"\n return length * width",// function_indentation: 0// }getBlock(code, blockPattern)
Section titled “getBlock(code, blockPattern)”Available via __helpers.python.getBlock(code, blockPattern), this function extracts code blocks matching specific patterns (if, for, while, etc.):
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');// Returns the if statement block
// Extract for blockconst forBlock = __helpers.python.getBlock(pythonCode, 'for');// Returns the for loop blockNotes 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