Bug 1533711 - Provide class with method chain in filter_utils.py for BinAST invalid file test. r=Yoric
authorTooru Fujisawa <arai_a@mac.com>
Mon, 11 Mar 2019 16:04:04 +0000
changeset 524398 6b9b50bc446d892779619254ce852f8b2f0c9bcf
parent 524397 9b356aa135ac45caa24e4ae7e8fe98d143a90d71
child 524399 e510d3ed595a6e133bfcdbb4db71b3eb74918839
push id2032
push userffxbld-merge
push dateMon, 13 May 2019 09:36:57 +0000
treeherdermozilla-release@455c1065dcbe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersYoric
bugs1533711
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1533711 - Provide class with method chain in filter_utils.py for BinAST invalid file test. r=Yoric Differential Revision: https://phabricator.services.mozilla.com/D22888
js/src/jsapi-tests/binast/invalid/lib/filter_utils.py
js/src/jsapi-tests/binast/invalid/tests/break-outside-while.py
js/src/jsapi-tests/binast/invalid/tests/catch-scope-missing.py
js/src/jsapi-tests/binast/invalid/tests/catch-scope-unmatched.py
js/src/jsapi-tests/binast/invalid/tests/continue-outside-while.py
js/src/jsapi-tests/binast/invalid/tests/function-decl-in-then.py
js/src/jsapi-tests/binast/invalid/tests/identifier-assign-target.py
js/src/jsapi-tests/binast/invalid/tests/identifier-binding.py
js/src/jsapi-tests/binast/invalid/tests/identifier-catch.py
js/src/jsapi-tests/binast/invalid/tests/identifier-declared-name.py
js/src/jsapi-tests/binast/invalid/tests/identifier-expression.py
js/src/jsapi-tests/binast/invalid/tests/identifier-function-name.py
js/src/jsapi-tests/binast/invalid/tests/identifier-positional-parameter.py
js/src/jsapi-tests/binast/invalid/tests/labelled-break-outside-while.py
js/src/jsapi-tests/binast/invalid/tests/labelled-continue-outside-while.py
js/src/jsapi-tests/binast/invalid/tests/param-index-duplicate.py
js/src/jsapi-tests/binast/invalid/tests/param-index-overflow.py
js/src/jsapi-tests/binast/invalid/tests/param-item-without-asserted-name.py
js/src/jsapi-tests/binast/invalid/tests/param-too-many.py
js/src/jsapi-tests/binast/invalid/tests/param-unmatching-name.py
js/src/jsapi-tests/binast/invalid/tests/wtf-8-field-name.py
js/src/jsapi-tests/binast/invalid/tests/wtf-8-interface-name.py
js/src/jsapi-tests/binast/invalid/tests/wtf-8-scope-name.py
js/src/jsapi-tests/binast/invalid/tests/wtf-8-variable-name.py
--- a/js/src/jsapi-tests/binast/invalid/lib/filter_utils.py
+++ b/js/src/jsapi-tests/binast/invalid/lib/filter_utils.py
@@ -1,314 +1,353 @@
 # Utilities to modify JSON encoded AST
 #
 # All functions raise exception on unexpected case, to avoid overlooking AST
 # change.
 
 import copy
 
 
-def assert_tagged_tuple(obj):
-    """
-    Assert that the object is a tagged tuple
+# Class to provide utility methods on JSON-encoded BinAST tree
+#
+# TODO: support enum, property key, float, bool, offset
+class wrap:
+    def __init__(self, obj):
+        self.obj = obj
 
-    :param obj (dict)
-           The tagged tuple
-    """
-    type_ = obj['@TYPE']
-    if type_ != 'tagged tuple':
-        raise Exception('expected a tagged tuple, got {}'.format(type_))
+    # ==== tagged tuple ====
 
+    def assert_tagged_tuple(self):
+        """
+        Assert that this object is a tagged tuple
 
-def assert_list(obj):
-    """
-    Assert that the object is a list
+        :return (wrap)
+                self
+        :raises Exception
+                If this is not a tagged tuple
+        """
+        type_ = self.obj['@TYPE']
+        if type_ != 'tagged tuple':
+            raise Exception('expected a tagged tuple, got {}'.format(type_))
+        return self
 
-    :param obj (dict)
-           The list
-    """
-    type_ = obj['@TYPE']
-    if type_ != 'list':
-        raise Exception('expected a list, got {}'.format(type_))
+    def copy(self):
+        """
+        Deep copy a tagged tuple
 
+        :return (wrap)
+                The wrapped copied tagged tuple
+        """
+        self.assert_tagged_tuple()
+        return wrap(copy.deepcopy(self.obj))
 
-def assert_unsigned_long(obj):
-    """
-    Assert that the object is a unsigned long
+    # ==== interface ====
 
-    :param obj (dict)
-           The unsigned long object
-    """
-    type_ = obj['@TYPE']
-    if type_ != 'unsigned long':
-        raise Exception('expected a unsigned long, got {}'.format(type_))
+    def assert_interface(self, expected_name):
+        """
+        Assert that this object is a tagged tuple for given interface
 
-
-def assert_string(obj):
-    """
-    Assert that the object is a string
+        :param expected_name (string)
+               The name of the interface
+        :return (wrap)
+                self
+        :raises Exception
+                If this is not an interface
+        """
+        self.assert_tagged_tuple()
+        actual_name = self.obj['@INTERFACE']
+        if actual_name != expected_name:
+            raise Exception('expected interface {}, got {}'.format(
+                expected_name, actual_name))
+        return self
 
-    :param obj (dict)
-           The string object
-    """
-    type_ = obj['@TYPE']
-    if type_ != 'string':
-        raise Exception('expected a string, got {}'.format(type_))
-
-
-def assert_identifier_name(obj):
-    """
-    Assert that the object is a identifier name
+    def field(self, name):
+        """
+        Returns the field of the tagged tuple.
 
-    :param obj (dict)
-           The identifier name object
-    """
-    type_ = obj['@TYPE']
-    if type_ != 'identifier name':
-        raise Exception('expected a identifier name, got {}'.format(type_))
+        :param name (string)
+               The name of the field to get
+        :return (wrap)
+                The wrapped field value
+        :raises Exception
+                If there's no such field
+        """
+        self.assert_tagged_tuple()
+        fields = self.obj['@FIELDS']
+        for field in fields:
+            if field['@FIELD_NAME'] == name:
+                return wrap(field['@FIELD_VALUE'])
+        raise Exception('No such field: {}'.format(name))
 
-
-def assert_interface(obj, expected_name):
-    """
-    Assert that the object is a tagged tuple for given interface
+    def append_field(self, name, value):
+        """
+        Append a field to the tagged tuple
 
-    :param obj (dict)
-           The tagged tuple
-    :param expected_name (string)
-           The name of the interface
-    """
-    assert_tagged_tuple(obj)
-    actual_name = obj['@INTERFACE']
-    if actual_name != expected_name:
-        raise Exception('expected {}, got {}'.format(expected_name, actual_name))
-
-
-def set_interface_name(obj, name):
-    """
-    Set the tagged tuple's interface name
+        :param name (string)
+               the name of the field to add
+        :param value (wrap)
+               the wrapped value of the field to add
+        :return (wrap)
+               the wrapped value
+        :raises Exception
+                If name or value are not correct type
+        """
+        self.assert_tagged_tuple()
+        if type(name) is not str and type(name) is not unicode:
+            raise Exception('passed name is not string: {}'.format(name))
+        if not isinstance(value, wrap):
+            raise Exception('passed value is not wrap: {}'.format(value))
+        fields = self.obj['@FIELDS']
+        fields.append({
+            '@FIELD_NAME': name,
+            '@FIELD_VALUE': value.obj,
+        })
+        return value
 
-    :param obj (dict)
-           The tagged tuple
-    :param name (string)
-           The name of the interface
-    """
-    assert_tagged_tuple(obj)
-    obj['@INTERFACE'] = name
-    if type(name) is not str and type(name) is not unicode:
-        raise Exception('passed value is not string: {}'.format(name))
+    def replace_field(self, name, value):
+        """
+        Replaces the field of the tagged tuple.
 
+        :param name (string)
+               the name of the field to replace
+        :param value (wrap)
+               the wrapped value of the field
+        :return (wrap)
+                the wrapped value
+        :raises Exception
+                If there's no such field
+        """
+        self.assert_tagged_tuple()
+        if not isinstance(value, wrap):
+            raise Exception('passed value is not wrap: {}'.format(value))
+        fields = self.obj['@FIELDS']
+        for field in fields:
+            if field['@FIELD_NAME'] == name:
+                field['@FIELD_VALUE'] = value.obj
+                return self
+        raise Exception('No such field: {}'.format(name))
 
-def get_field(obj, name):
-    """
-    Returns the field of the tagged tuple.
+    def remove_field(self, name):
+        """
+        Removes the field from the tagged tuple
 
-    :param obj (dict)
-           The tagged tuple
-    :param name (string)
-           The name of the field to get
-    :return (dict)
-            The field value
-    :raises Exception
-            If there's no such field
-    """
-    assert_tagged_tuple(obj)
-    fields = obj['@FIELDS']
-    for field in fields:
-        if field['@FIELD_NAME'] == name:
-            return field['@FIELD_VALUE']
-    raise Exception('No such field: {}'.format(name))
+        :param name (string)
+               the name of the field to remove
+        :return (wrap)
+                the wrapped removed field value
+        :raises Exception
+                If there's no such field
+        """
+        self.assert_tagged_tuple()
+        i = 0
+        fields = self.obj['@FIELDS']
+        for field in fields:
+            if field['@FIELD_NAME'] == name:
+                del fields[i]
+                return wrap(field['@FIELD_VALUE'])
+            i += 1
+        raise Exception('No such field: {}'.format(name))
 
-
-def replace_field(obj, name, value):
-    """
-    Replaces the field of the tagged tuple.
+    def set_interface_name(self, name):
+        """
+        Set the tagged tuple's interface name
 
-    :param obj (dict)
-           the tagged tuple
-    :param name (string)
-           the name of the field to replace
-    :param value (dict)
-           the value of the field
-    :raises Exception
-            If there's no such field
-    """
-    assert_tagged_tuple(obj)
-    fields = obj['@FIELDS']
-    for field in fields:
-        if field['@FIELD_NAME'] == name:
-            field['@FIELD_VALUE'] = value
-            return
-    raise Exception('No such field: {}'.format(name))
+        :param name (string)
+               The name of the interface
+        :return (wrap)
+                self
+        :raises Exception
+                If name is not correct type
+        """
+        self.assert_tagged_tuple()
+        self.obj['@INTERFACE'] = name
+        if type(name) is not str and type(name) is not unicode:
+            raise Exception('passed value is not string: {}'.format(name))
+        return self
 
+    # ==== list ====
 
-def remove_field(obj, name):
-    """
-    Removes the field from the tagged tuple
+    def assert_list(self):
+        """
+        Assert that this object is a list
+
+        :return (wrap)
+                self
+        :raises Exception
+                If this is not a list
+        """
+        type_ = self.obj['@TYPE']
+        if type_ != 'list':
+            raise Exception('expected a list, got {}'.format(type_))
+        return self
 
-    :param obj (dict)
-           the tagged tuple
-    :param name (string)
-           the name of the field to remove
-    :raises Exception
-            If there's no such field
-    """
-    assert_tagged_tuple(obj)
-    i = 0
-    fields = obj['@FIELDS']
-    for field in fields:
-        if field['@FIELD_NAME'] == name:
-            del fields[i]
-            return
-        i += 1
-    raise Exception('No such field: {}'.format(name))
+    def elem(self, index):
+        """
+        Returns the element of the list.
 
+        :param index (int)
+               The index of the element to get
+        :return (wrap)
+                The wrapped element value
+        :raises Exception
+                On out of bound access
+        """
+        self.assert_list()
+        elems = self.obj['@VALUE']
+        if index >= len(elems):
+            raise Exception('Out of bound: {} >= {}'.format(index, len(elems)))
+        return wrap(elems[index])
 
-def append_field(obj, name, value):
-    """
-    Append a field to the tagged tuple
+    def append_elem(self, elem):
+        """
+        Appends the element to the list.
 
-    :param obj (dict)
-           the tagged tuple
-    :param name (string)
-           the name of the field to add
-    :param value (dict)
-           the value of the field to add
-    """
-    assert_tagged_tuple(obj)
-    fields = obj['@FIELDS']
-    if type(name) is not str and type(name) is not unicode:
-        raise Exception('passed name is not string: {}'.format(name))
-    if type(value) is not dict:
-        raise Exception('passed value is not dict: {}'.format(value))
-    fields.append({
-        '@FIELD_NAME': name,
-        '@FIELD_VALUE': value,
-    })
+        :param elem (wrap)
+               the wrapped value to be added to the list
+        :return (wrap)
+                The wrapped appended element
+        """
+        self.assert_list()
+        if not isinstance(elem, wrap):
+            raise Exception('passed value is not wrap: {}'.format(elem))
+        elems = self.obj['@VALUE']
+        elems.append(elem.obj)
+        return elem
 
+    def replace_elem(self, index, elem):
+        """
+        Replaces the element of the list.
 
-def get_element(obj, i):
-    """
-    Returns the element of the list.
-
-    :param obj (dict)
-           The list
-    :param i (int)
-           The indef of the element to get
-    :return (dict)
-            The element value
-    :raises Exception
-            On out of bound access
-    """
-    assert_list(obj)
-    elements = obj['@VALUE']
-    if i >= len(elements):
-        raise Exception('Out of bound: {} >= {}'.format(i, len(elements)))
-    return elements[i]
-
+        :param index (int)
+               The index of the element to replace
+        :param elem (wrap)
+               the wrapped value of the element
+        :return (wrap)
+                The wrapped replaced element
+        :raises Exception
+                On out of bound access
+        """
+        self.assert_list()
+        if not isinstance(elem, wrap):
+            raise Exception('passed value is not wrap: {}'.format(elem))
+        elems = self.obj['@VALUE']
+        if index >= len(elems):
+            raise Exception('Out of bound: {} >= {}'.format(index, len(elems)))
+        elems[index] = elem.obj
+        return elem
 
-def replace_element(obj, i, value):
-    """
-    Replaces the element of the list.
+    def remove_elem(self, index):
+        """
+        Removes the element from the list
 
-    :param obj (dict)
-           the list
-    :param i (int)
-           The indef of the element to replace
-    :param value (dict)
-           the value of the element
-    :raises Exception
-            On out of bound access
-    """
-    assert_list(obj)
-    elements = obj['@VALUE']
-    if i >= len(elements):
-        raise Exception('Out of bound: {} >= {}'.format(i, len(elements)))
-    elements[i] = value
+        :param index (int)
+               The index of the element to remove
+        :return (wrap)
+                The wrapped removed element
+        :raises Exception
+                On out of bound access
+        """
+        self.assert_list()
+        elems = self.obj['@VALUE']
+        if index >= len(elems):
+            raise Exception('Out of bound: {} >= {}'.format(index, len(elems)))
+        elem = elems[index]
+        del elems[index]
+        return wrap(elem)
 
+    # ==== string ====
 
-def append_element(obj, value):
-    """
-    Appends the element to the list.
+    def assert_string(self):
+        """
+        Assert that this object is a string
 
-    :param obj (dict)
-           the list
-    :param value (dict)
-           the value to be added to the list
-    """
-    assert_list(obj)
-    elements = obj['@VALUE']
-    elements.append(value)
+        :return (wrap)
+                self
+        :raises Exception
+                If this is not a string
+        """
+        type_ = self.obj['@TYPE']
+        if type_ != 'string':
+            raise Exception('expected a string, got {}'.format(type_))
+        return self
 
-
-def remove_element(obj, i):
-    """
-    Removes the element from the list
+    def set_string(self, value):
+        """
+        Set string value
 
-    :param obj (dict)
-           the list
-    :param i (int)
-           The indef of the element to remove
-    :raises Exception
-            On out of bound access
-    """
-    assert_list(obj)
-    elements = obj['@VALUE']
-    if i >= len(elements):
-        raise Exception('Out of bound: {} >= {}'.format(i, len(elements)))
-    del elements[i]
+        :param value (int)
+               the value to set
+        :return (wrap)
+                self
+        :raises Exception
+                If value is not correct type
+        """
+        self.assert_string()
+        if type(value) is not str and type(value) is not unicode:
+            raise Exception('passed value is not string: {}'.format(value))
+        self.obj['@VALUE'] = value
+        return self
 
+    # ==== identifier name ====
 
-def copy_tagged_tuple(obj):
-    """
-    Deep copy a tagged tuple
+    def assert_identifier_name(self):
+        """
+        Assert that this object is a identifier name
 
-    :param obj (dict)
-           the tagged tuple
-    :return (dict)
-            The copied tagged tuple
-    """
-    assert_tagged_tuple(obj)
-    return copy.deepcopy(obj)
-
+        :return (wrap)
+                self
+        :raises Exception
+                If this is not an identifier name
+        """
+        type_ = self.obj['@TYPE']
+        if type_ != 'identifier name':
+            raise Exception('expected a identifier name, got {}'.format(type_))
+        return self
 
-def set_unsigned_long(obj, value):
-    """
-    Set unsigned long value
+    def set_identifier_name(self, value):
+        """
+        Set identifier name value
 
-    :param obj (dict)
-           the unsigned long object
-    :param value (int)
-           the value to set
-    """
-    assert_unsigned_long(obj)
-    if type(value) is not int:
-        raise Exception('passed value is not int: {}'.format(value))
-    obj['@VALUE'] = value
+        :param value (int)
+               the value to set
+        :return (wrap)
+                self
+        :raises Exception
+                If value is not correct type
+        """
+        self.assert_identifier_name()
+        if type(value) is not str and type(value) is not unicode:
+            raise Exception('passed value is not string: {}'.format(value))
+        self.obj['@VALUE'] = value
+        return self
 
+    # ==== unsigned long ====
 
-def set_string(obj, value):
-    """
-    Set string value
+    def assert_unsigned_long(self):
+        """
+        Assert that this object is a unsigned long
 
-    :param obj (dict)
-           the string object
-    :param value (int)
-           the value to set
-    """
-    assert_string(obj)
-    if type(value) is not str and type(value) is not unicode:
-        raise Exception('passed value is not string: {}'.format(value))
-    obj['@VALUE'] = value
-
+        :return (wrap)
+                self
+        :raises Exception
+                If this is not an unsigned long
+        """
+        type_ = self.obj['@TYPE']
+        if type_ != 'unsigned long':
+            raise Exception('expected a unsigned long, got {}'.format(type_))
+        return self
 
-def set_identifier_name(obj, value):
-    """
-    Set identifier name value
+    def set_unsigned_long(self, value):
+        """
+        Set unsigned long value
 
-    :param obj (dict)
-           the identifier name object
-    :param value (int)
-           the value to set
-    """
-    assert_identifier_name(obj)
-    if type(value) is not str and type(value) is not unicode:
-        raise Exception('passed value is not string: {}'.format(value))
-    obj['@VALUE'] = value
+        :param value (int)
+               the value to set
+        :return (wrap)
+                self
+        :raises Exception
+                If value is not correct type
+        """
+        self.assert_unsigned_long()
+        if type(value) is not int:
+            raise Exception('passed value is not int: {}'.format(value))
+        self.obj['@VALUE'] = value
+        return self
--- a/js/src/jsapi-tests/binast/invalid/tests/break-outside-while.py
+++ b/js/src/jsapi-tests/binast/invalid/tests/break-outside-while.py
@@ -1,21 +1,20 @@
 def filter_ast(ast):
     # Move a break statement out of a while loop.
     import filter_utils as utils
 
-    utils.assert_interface(ast, 'Script')
-    global_stmts = utils.get_field(ast, 'statements')
-
-    while_stmt = utils.get_element(global_stmts, 0)
-    utils.assert_interface(while_stmt, 'WhileStatement')
+    global_stmts = utils.wrap(ast) \
+        .assert_interface('Script') \
+        .field('statements')
 
-    while_body = utils.get_field(while_stmt, 'body')
-    utils.assert_interface(while_body, 'Block')
-    while_body_stmts = utils.get_field(while_body, 'statements')
+    break_stmt = global_stmts \
+        .elem(0) \
+        .assert_interface('WhileStatement') \
+        .field('body') \
+        .assert_interface('Block') \
+        .field('statements') \
+        .remove_elem(0) \
+        .assert_interface('BreakStatement')
 
-    break_stmt = utils.get_element(while_body_stmts, 0)
-    utils.assert_interface(break_stmt, 'BreakStatement')
-
-    utils.remove_element(while_body_stmts, 0)
-    utils.append_element(global_stmts, break_stmt)
+    global_stmts.append_elem(break_stmt)
 
     return ast
--- a/js/src/jsapi-tests/binast/invalid/tests/catch-scope-missing.py
+++ b/js/src/jsapi-tests/binast/invalid/tests/catch-scope-missing.py
@@ -1,20 +1,17 @@
 def filter_ast(ast):
     # catch with missing AssertedBoundName
     import filter_utils as utils
 
-    utils.assert_interface(ast, 'Script')
-    global_stmts = utils.get_field(ast, 'statements')
-
-    try_stmt = utils.get_element(global_stmts, 0)
-    utils.assert_interface(try_stmt, 'TryCatchStatement')
-
-    catch = utils.get_field(try_stmt, 'catchClause')
-    utils.assert_interface(catch, 'CatchClause')
-
-    scope = utils.get_field(catch, 'bindingScope')
-    utils.assert_interface(scope, 'AssertedBoundNamesScope')
-
-    names = utils.get_field(scope, 'boundNames')
-    utils.remove_element(names, 0)
+    utils.wrap(ast) \
+        .assert_interface('Script') \
+        .field('statements') \
+        .elem(0) \
+        .assert_interface('TryCatchStatement') \
+        .field('catchClause') \
+        .assert_interface('CatchClause') \
+        .field('bindingScope') \
+        .assert_interface('AssertedBoundNamesScope') \
+        .field('boundNames') \
+        .remove_elem(0)
 
     return ast
--- a/js/src/jsapi-tests/binast/invalid/tests/catch-scope-unmatched.py
+++ b/js/src/jsapi-tests/binast/invalid/tests/catch-scope-unmatched.py
@@ -1,25 +1,20 @@
 def filter_ast(ast):
     # catch with unmatched AssertedBoundName
     import filter_utils as utils
 
-    utils.assert_interface(ast, 'Script')
-    global_stmts = utils.get_field(ast, 'statements')
-
-    try_stmt = utils.get_element(global_stmts, 0)
-    utils.assert_interface(try_stmt, 'TryCatchStatement')
-
-    catch = utils.get_field(try_stmt, 'catchClause')
-    utils.assert_interface(catch, 'CatchClause')
-
-    scope = utils.get_field(catch, 'bindingScope')
-    utils.assert_interface(scope, 'AssertedBoundNamesScope')
-
-    names = utils.get_field(scope, 'boundNames')
-    bound_name = utils.get_element(names, 0)
-    utils.assert_interface(bound_name, 'AssertedBoundName')
-
-    name = utils.get_field(bound_name, 'name')
-
-    utils.set_identifier_name(name, 'b')
+    utils.wrap(ast) \
+        .assert_interface('Script') \
+        .field('statements') \
+        .elem(0) \
+        .assert_interface('TryCatchStatement') \
+        .field('catchClause') \
+        .assert_interface('CatchClause') \
+        .field('bindingScope') \
+        .assert_interface('AssertedBoundNamesScope') \
+        .field('boundNames') \
+        .elem(0) \
+        .assert_interface('AssertedBoundName') \
+        .field('name') \
+        .set_identifier_name('b')
 
     return ast
--- a/js/src/jsapi-tests/binast/invalid/tests/continue-outside-while.py
+++ b/js/src/jsapi-tests/binast/invalid/tests/continue-outside-while.py
@@ -1,21 +1,19 @@
 def filter_ast(ast):
     # Move a continue statement out of a while loop.
     import filter_utils as utils
 
-    utils.assert_interface(ast, 'Script')
-    global_stmts = utils.get_field(ast, 'statements')
-
-    while_stmt = utils.get_element(global_stmts, 0)
-    utils.assert_interface(while_stmt, 'WhileStatement')
+    global_stmts = utils.wrap(ast) \
+        .assert_interface('Script') \
+        .field('statements')
 
-    while_body = utils.get_field(while_stmt, 'body')
-    utils.assert_interface(while_body, 'Block')
-    while_body_stmts = utils.get_field(while_body, 'statements')
+    continue_stmt = global_stmts.elem(0) \
+        .assert_interface('WhileStatement') \
+        .field('body') \
+        .assert_interface('Block') \
+        .field('statements') \
+        .remove_elem(0) \
+        .assert_interface('ContinueStatement')
 
-    continue_stmt = utils.get_element(while_body_stmts, 0)
-    utils.assert_interface(continue_stmt, 'ContinueStatement')
-
-    utils.remove_element(while_body_stmts, 0)
-    utils.append_element(global_stmts, continue_stmt)
+    global_stmts.append_elem(continue_stmt)
 
     return ast
--- a/js/src/jsapi-tests/binast/invalid/tests/function-decl-in-then.py
+++ b/js/src/jsapi-tests/binast/invalid/tests/function-decl-in-then.py
@@ -1,21 +1,21 @@
 def filter_ast(ast):
     # Move function inside then-block, without fixing scope.
     import filter_utils as utils
 
-    utils.assert_interface(ast, 'Script')
-    global_stmts = utils.get_field(ast, 'statements')
+    global_stmts = utils.wrap(ast) \
+        .assert_interface('Script') \
+        .field('statements')
 
-    fun_stmt = utils.get_element(global_stmts, 0)
-    utils.assert_interface(fun_stmt, 'EagerFunctionDeclaration')
+    fun_decl = global_stmts \
+        .elem(0) \
+        .assert_interface('EagerFunctionDeclaration')
 
-    if_stmt = utils.get_element(global_stmts, 1)
-    utils.assert_interface(if_stmt, 'IfStatement')
-
-    block_stmt = utils.get_field(if_stmt, 'consequent')
-    utils.assert_interface(block_stmt, 'Block')
-
-    block_stmts = utils.get_field(block_stmt, 'statements')
-
-    utils.append_element(block_stmts, utils.copy_tagged_tuple(fun_stmt))
+    global_stmts \
+        .elem(1) \
+        .assert_interface('IfStatement') \
+        .field('consequent') \
+        .assert_interface('Block') \
+        .field('statements') \
+        .append_elem(fun_decl.copy())
 
     return ast
--- a/js/src/jsapi-tests/binast/invalid/tests/identifier-assign-target.py
+++ b/js/src/jsapi-tests/binast/invalid/tests/identifier-assign-target.py
@@ -1,21 +1,17 @@
 def filter_ast(ast):
     # AssignmentTargetIdentifier with non-identifier string.
     import filter_utils as utils
 
-    utils.assert_interface(ast, 'Script')
-    global_stmts = utils.get_field(ast, 'statements')
-
-    expr_stmt = utils.get_element(global_stmts, 0)
-    utils.assert_interface(expr_stmt, 'ExpressionStatement')
-
-    assign_expr = utils.get_field(expr_stmt, 'expression')
-    utils.assert_interface(assign_expr, 'AssignmentExpression')
-
-    binding = utils.get_field(assign_expr, 'binding')
-    utils.assert_interface(binding, 'AssignmentTargetIdentifier')
-
-    name = utils.get_field(binding, 'name')
-
-    utils.set_identifier_name(name, '1')
+    utils.wrap(ast) \
+        .assert_interface('Script') \
+        .field('statements') \
+        .elem(0) \
+        .assert_interface('ExpressionStatement') \
+        .field('expression') \
+        .assert_interface('AssignmentExpression') \
+        .field('binding') \
+        .assert_interface('AssignmentTargetIdentifier') \
+        .field('name') \
+        .set_identifier_name('1')
 
     return ast
--- a/js/src/jsapi-tests/binast/invalid/tests/identifier-binding.py
+++ b/js/src/jsapi-tests/binast/invalid/tests/identifier-binding.py
@@ -1,26 +1,23 @@
 def filter_ast(ast):
     # BindingIdentifier with non-identifier string.
     import filter_utils as utils
 
-    utils.assert_interface(ast, 'Script')
-    global_stmts = utils.get_field(ast, 'statements')
-
-    var_decl = utils.get_element(global_stmts, 0)
-    utils.assert_interface(var_decl, 'VariableDeclaration')
-
-    decls = utils.get_field(var_decl, 'declarators')
+    decls = utils.wrap(ast) \
+        .assert_interface('Script') \
+        .field('statements') \
+        .elem(0) \
+        .assert_interface('VariableDeclaration') \
+        .field('declarators')
 
-    decl = utils.get_element(decls, 0)
-    utils.assert_interface(decl, 'VariableDeclarator')
-
-    copied_decl = utils.copy_tagged_tuple(decl)
-    utils.append_element(decls, copied_decl)
+    copied_decl = decls.elem(0) \
+        .assert_interface('VariableDeclarator') \
+        .copy()
 
-    binding = utils.get_field(copied_decl, 'binding')
-    utils.assert_interface(binding, 'BindingIdentifier')
+    decls.append_elem(copied_decl)
 
-    name = utils.get_field(binding, 'name')
-
-    utils.set_identifier_name(name, '1')
+    copied_decl.field('binding') \
+        .assert_interface('BindingIdentifier') \
+        .field('name') \
+        .set_identifier_name('1')
 
     return ast
--- a/js/src/jsapi-tests/binast/invalid/tests/identifier-catch.py
+++ b/js/src/jsapi-tests/binast/invalid/tests/identifier-catch.py
@@ -1,25 +1,20 @@
 def filter_ast(ast):
     # AssertedBoundName with non-identifier string.
     import filter_utils as utils
 
-    utils.assert_interface(ast, 'Script')
-    global_stmts = utils.get_field(ast, 'statements')
-
-    try_stmt = utils.get_element(global_stmts, 0)
-    utils.assert_interface(try_stmt, 'TryCatchStatement')
-
-    catch = utils.get_field(try_stmt, 'catchClause')
-    utils.assert_interface(catch, 'CatchClause')
-
-    scope = utils.get_field(catch, 'bindingScope')
-    utils.assert_interface(scope, 'AssertedBoundNamesScope')
-
-    names = utils.get_field(scope, 'boundNames')
-    bound_name = utils.get_element(names, 0)
-    utils.assert_interface(bound_name, 'AssertedBoundName')
-
-    name = utils.get_field(bound_name, 'name')
-
-    utils.set_identifier_name(name, '1')
+    utils.wrap(ast) \
+        .assert_interface('Script') \
+        .field('statements') \
+        .elem(0) \
+        .assert_interface('TryCatchStatement') \
+        .field('catchClause') \
+        .assert_interface('CatchClause') \
+        .field('bindingScope') \
+        .assert_interface('AssertedBoundNamesScope') \
+        .field('boundNames') \
+        .elem(0) \
+        .assert_interface('AssertedBoundName') \
+        .field('name') \
+        .set_identifier_name('1')
 
     return ast
--- a/js/src/jsapi-tests/binast/invalid/tests/identifier-declared-name.py
+++ b/js/src/jsapi-tests/binast/invalid/tests/identifier-declared-name.py
@@ -1,20 +1,20 @@
 def filter_ast(ast):
     # AssertedDeclaredName with non-identifier string.
     import filter_utils as utils
 
-    utils.assert_interface(ast, 'Script')
-    scope = utils.get_field(ast, 'scope')
-    utils.assert_interface(scope, 'AssertedScriptGlobalScope')
+    decl_names = utils.wrap(ast) \
+        .assert_interface('Script') \
+        .field('scope') \
+        .assert_interface('AssertedScriptGlobalScope') \
+        .field('declaredNames') \
 
-    decl_names = utils.get_field(scope, 'declaredNames')
-    decl_name = utils.get_element(decl_names, 0)
-    utils.assert_interface(decl_name, 'AssertedDeclaredName')
+    copied_decl_name = decl_names.elem(0) \
+        .assert_interface('AssertedDeclaredName') \
+        .copy()
 
-    copied_decl_name = utils.copy_tagged_tuple(decl_name)
-    utils.append_element(decl_names, copied_decl_name)
+    decl_names.append_elem(copied_decl_name)
 
-    name = utils.get_field(copied_decl_name, 'name')
-
-    utils.set_identifier_name(name, '1')
+    copied_decl_name.field('name') \
+        .set_identifier_name('1')
 
     return ast
--- a/js/src/jsapi-tests/binast/invalid/tests/identifier-expression.py
+++ b/js/src/jsapi-tests/binast/invalid/tests/identifier-expression.py
@@ -1,21 +1,17 @@
 def filter_ast(ast):
     # IdentifierExpression with non-identifier string.
     import filter_utils as utils
 
-    utils.assert_interface(ast, 'Script')
-    global_stmts = utils.get_field(ast, 'statements')
-
-    expr_stmt = utils.get_element(global_stmts, 0)
-    utils.assert_interface(expr_stmt, 'ExpressionStatement')
-
-    assign_expr = utils.get_field(expr_stmt, 'expression')
-    utils.assert_interface(assign_expr, 'AssignmentExpression')
-
-    expr = utils.get_field(assign_expr, 'expression')
-    utils.assert_interface(expr, 'IdentifierExpression')
-
-    name = utils.get_field(expr, 'name')
-
-    utils.set_identifier_name(name, '1')
+    utils.wrap(ast) \
+        .assert_interface('Script') \
+        .field('statements') \
+        .elem(0) \
+        .assert_interface('ExpressionStatement') \
+        .field('expression') \
+        .assert_interface('AssignmentExpression') \
+        .field('expression') \
+        .assert_interface('IdentifierExpression') \
+        .field('name') \
+        .set_identifier_name('1')
 
     return ast
--- a/js/src/jsapi-tests/binast/invalid/tests/identifier-function-name.py
+++ b/js/src/jsapi-tests/binast/invalid/tests/identifier-function-name.py
@@ -1,18 +1,15 @@
 def filter_ast(ast):
     # AssignmentTargetIdentifier with non-identifier string.
     import filter_utils as utils
 
-    utils.assert_interface(ast, 'Script')
-    global_stmts = utils.get_field(ast, 'statements')
-
-    fun_decl = utils.get_element(global_stmts, 0)
-    utils.assert_interface(fun_decl, 'EagerFunctionDeclaration')
-
-    binding = utils.get_field(fun_decl, 'name')
-    utils.assert_interface(binding, 'BindingIdentifier')
-
-    name = utils.get_field(binding, 'name')
-
-    utils.set_identifier_name(name, '1')
+    utils.wrap(ast) \
+        .assert_interface('Script') \
+        .field('statements') \
+        .elem(0) \
+        .assert_interface('EagerFunctionDeclaration') \
+        .field('name') \
+        .assert_interface('BindingIdentifier') \
+        .field('name') \
+        .set_identifier_name('1')
 
     return ast
--- a/js/src/jsapi-tests/binast/invalid/tests/identifier-positional-parameter.py
+++ b/js/src/jsapi-tests/binast/invalid/tests/identifier-positional-parameter.py
@@ -1,25 +1,20 @@
 def filter_ast(ast):
     # AssertedPositionalParameterName with non-identifier string.
     import filter_utils as utils
 
-    utils.assert_interface(ast, 'Script')
-    global_stmts = utils.get_field(ast, 'statements')
-
-    fun_decl = utils.get_element(global_stmts, 0)
-    utils.assert_interface(fun_decl, 'EagerFunctionDeclaration')
-
-    contents = utils.get_field(fun_decl, 'contents')
-    utils.assert_interface(contents, 'FunctionOrMethodContents')
-
-    scope = utils.get_field(contents, 'parameterScope')
-    utils.assert_interface(scope, 'AssertedParameterScope')
-
-    param_names = utils.get_field(scope, 'paramNames')
-    param_name = utils.get_element(param_names, 0)
-    utils.assert_interface(param_name, 'AssertedPositionalParameterName')
-
-    name = utils.get_field(param_name, 'name')
-
-    utils.set_identifier_name(name, '1')
+    utils.wrap(ast) \
+        .assert_interface('Script') \
+        .field('statements') \
+        .elem(0) \
+        .assert_interface('EagerFunctionDeclaration') \
+        .field('contents') \
+        .assert_interface('FunctionOrMethodContents') \
+        .field('parameterScope') \
+        .assert_interface('AssertedParameterScope') \
+        .field('paramNames') \
+        .elem(0) \
+        .assert_interface('AssertedPositionalParameterName') \
+        .field('name') \
+        .set_identifier_name('1')
 
     return ast
--- a/js/src/jsapi-tests/binast/invalid/tests/labelled-break-outside-while.py
+++ b/js/src/jsapi-tests/binast/invalid/tests/labelled-break-outside-while.py
@@ -1,24 +1,21 @@
 def filter_ast(ast):
     # Move a break statement out of a while loop.
     import filter_utils as utils
 
-    utils.assert_interface(ast, 'Script')
-    global_stmts = utils.get_field(ast, 'statements')
-
-    labelled_stmt = utils.get_element(global_stmts, 0)
-    utils.assert_interface(labelled_stmt, 'LabelledStatement')
-
-    while_stmt = utils.get_field(labelled_stmt, 'body')
-    utils.assert_interface(while_stmt, 'WhileStatement')
+    global_stmts = utils.wrap(ast) \
+        .assert_interface('Script') \
+        .field('statements')
 
-    while_body = utils.get_field(while_stmt, 'body')
-    utils.assert_interface(while_body, 'Block')
-    while_body_stmts = utils.get_field(while_body, 'statements')
+    break_stmt = global_stmts.elem(0) \
+        .assert_interface('LabelledStatement') \
+        .field('body') \
+        .assert_interface('WhileStatement') \
+        .field('body') \
+        .assert_interface('Block') \
+        .field('statements') \
+        .remove_elem(0) \
+        .assert_interface('BreakStatement')
 
-    break_stmt = utils.get_element(while_body_stmts, 0)
-    utils.assert_interface(break_stmt, 'BreakStatement')
-
-    utils.remove_element(while_body_stmts, 0)
-    utils.append_element(global_stmts, break_stmt)
+    global_stmts.append_elem(break_stmt)
 
     return ast
--- a/js/src/jsapi-tests/binast/invalid/tests/labelled-continue-outside-while.py
+++ b/js/src/jsapi-tests/binast/invalid/tests/labelled-continue-outside-while.py
@@ -1,24 +1,21 @@
 def filter_ast(ast):
     # Move a continue statement out of a while loop.
     import filter_utils as utils
 
-    utils.assert_interface(ast, 'Script')
-    global_stmts = utils.get_field(ast, 'statements')
-
-    labelled_stmt = utils.get_element(global_stmts, 0)
-    utils.assert_interface(labelled_stmt, 'LabelledStatement')
-
-    while_stmt = utils.get_field(labelled_stmt, 'body')
-    utils.assert_interface(while_stmt, 'WhileStatement')
+    global_stmts = utils.wrap(ast) \
+        .assert_interface('Script') \
+        .field('statements')
 
-    while_body = utils.get_field(while_stmt, 'body')
-    utils.assert_interface(while_body, 'Block')
-    while_body_stmts = utils.get_field(while_body, 'statements')
+    continue_stmt = global_stmts.elem(0) \
+        .assert_interface('LabelledStatement') \
+        .field('body') \
+        .assert_interface('WhileStatement') \
+        .field('body') \
+        .assert_interface('Block') \
+        .field('statements') \
+        .remove_elem(0) \
+        .assert_interface('ContinueStatement')
 
-    continue_stmt = utils.get_element(while_body_stmts, 0)
-    utils.assert_interface(continue_stmt, 'ContinueStatement')
-
-    utils.remove_element(while_body_stmts, 0)
-    utils.append_element(global_stmts, continue_stmt)
+    global_stmts.append_elem(continue_stmt)
 
     return ast
--- a/js/src/jsapi-tests/binast/invalid/tests/param-index-duplicate.py
+++ b/js/src/jsapi-tests/binast/invalid/tests/param-index-duplicate.py
@@ -1,24 +1,20 @@
 def filter_ast(ast):
     # 2 AssertedPositionalParameterName with same index.
     import filter_utils as utils
 
-    utils.assert_interface(ast, 'Script')
-    global_stmts = utils.get_field(ast, 'statements')
-
-    func_stmt = utils.get_element(global_stmts, 0)
-    utils.assert_interface(func_stmt, 'EagerFunctionDeclaration')
-
-    func_contents = utils.get_field(func_stmt, 'contents')
-    utils.assert_interface(func_contents, 'FunctionOrMethodContents')
-
-    param_scope = utils.get_field(func_contents, 'parameterScope')
-    utils.assert_interface(param_scope, 'AssertedParameterScope')
-    param_names = utils.get_field(param_scope, 'paramNames')
-
-    param_name = utils.get_element(param_names, 1)
-    utils.assert_interface(param_name, 'AssertedPositionalParameterName')
-
-    index = utils.get_field(param_name, "index")
-    utils.set_unsigned_long(index, 0)
+    utils.wrap(ast) \
+        .assert_interface('Script') \
+        .field('statements') \
+        .elem(0) \
+        .assert_interface('EagerFunctionDeclaration') \
+        .field('contents') \
+        .assert_interface('FunctionOrMethodContents') \
+        .field('parameterScope') \
+        .assert_interface('AssertedParameterScope') \
+        .field('paramNames') \
+        .elem(1) \
+        .assert_interface('AssertedPositionalParameterName') \
+        .field('index') \
+        .set_unsigned_long(0)
 
     return ast
--- a/js/src/jsapi-tests/binast/invalid/tests/param-index-overflow.py
+++ b/js/src/jsapi-tests/binast/invalid/tests/param-index-overflow.py
@@ -1,24 +1,20 @@
 def filter_ast(ast):
     # AssertedPositionalParameterName.index >= ARGNO_LIMIT - 1.
     import filter_utils as utils
 
-    utils.assert_interface(ast, 'Script')
-    global_stmts = utils.get_field(ast, 'statements')
-
-    func_stmt = utils.get_element(global_stmts, 0)
-    utils.assert_interface(func_stmt, 'EagerFunctionDeclaration')
-
-    func_contents = utils.get_field(func_stmt, 'contents')
-    utils.assert_interface(func_contents, 'FunctionOrMethodContents')
-
-    param_scope = utils.get_field(func_contents, 'parameterScope')
-    utils.assert_interface(param_scope, 'AssertedParameterScope')
-    param_names = utils.get_field(param_scope, 'paramNames')
-
-    param_name = utils.get_element(param_names, 0)
-    utils.assert_interface(param_name, 'AssertedPositionalParameterName')
-
-    index = utils.get_field(param_name, "index")
-    utils.set_unsigned_long(index, 65536)
+    utils.wrap(ast) \
+        .assert_interface('Script') \
+        .field('statements') \
+        .elem(0) \
+        .assert_interface('EagerFunctionDeclaration') \
+        .field('contents') \
+        .assert_interface('FunctionOrMethodContents') \
+        .field('parameterScope') \
+        .assert_interface('AssertedParameterScope') \
+        .field('paramNames') \
+        .elem(0) \
+        .assert_interface('AssertedPositionalParameterName') \
+        .field('index') \
+        .set_unsigned_long(65536)
 
     return ast
--- a/js/src/jsapi-tests/binast/invalid/tests/param-item-without-asserted-name.py
+++ b/js/src/jsapi-tests/binast/invalid/tests/param-item-without-asserted-name.py
@@ -1,28 +1,25 @@
 def filter_ast(ast):
     # Add unmatching PositionalFormalParameter
     import filter_utils as utils
 
-    utils.assert_interface(ast, 'Script')
-    global_stmts = utils.get_field(ast, 'statements')
-
-    func_stmt = utils.get_element(global_stmts, 0)
-    utils.assert_interface(func_stmt, 'EagerFunctionDeclaration')
-
-    func_contents = utils.get_field(func_stmt, 'contents')
-    utils.assert_interface(func_contents, 'FunctionOrMethodContents')
+    params_items = utils.wrap(ast) \
+        .assert_interface('Script') \
+        .field('statements') \
+        .elem(0) \
+        .assert_interface('EagerFunctionDeclaration') \
+        .field('contents') \
+        .assert_interface('FunctionOrMethodContents') \
+        .field('params') \
+        .assert_interface('FormalParameters') \
+        .field('items')
 
-    params = utils.get_field(func_contents, 'params')
-    utils.assert_interface(params, 'FormalParameters')
-    params_items = utils.get_field(params, 'items')
-
-    param = utils.get_element(params_items, 0)
-    utils.assert_interface(param, 'BindingIdentifier')
+    copied_param = params_items.elem(0) \
+        .assert_interface('BindingIdentifier') \
+        .copy()
 
-    copied_param = utils.copy_tagged_tuple(param)
+    copied_param.field('name') \
+        .set_identifier_name('b')
 
-    name = utils.get_field(copied_param, "name")
-    utils.set_identifier_name(name, "b")
-
-    utils.append_element(params_items, copied_param)
+    params_items.append_elem(copied_param)
 
     return ast
--- a/js/src/jsapi-tests/binast/invalid/tests/param-too-many.py
+++ b/js/src/jsapi-tests/binast/invalid/tests/param-too-many.py
@@ -1,31 +1,30 @@
 def filter_ast(ast):
     # Duplicate parameter up to 65536 entries.
     import filter_utils as utils
 
-    utils.assert_interface(ast, 'Script')
-    global_stmts = utils.get_field(ast, 'statements')
-
-    func_stmt = utils.get_element(global_stmts, 0)
-    utils.assert_interface(func_stmt, 'EagerFunctionDeclaration')
+    param_names = utils.wrap(ast) \
+        .assert_interface('Script') \
+        .field('statements') \
+        .elem(0) \
+        .assert_interface('EagerFunctionDeclaration') \
+        .field('contents') \
+        .assert_interface('FunctionOrMethodContents') \
+        .field('parameterScope') \
+        .assert_interface('AssertedParameterScope') \
+        .field('paramNames')
 
-    func_contents = utils.get_field(func_stmt, 'contents')
-    utils.assert_interface(func_contents, 'FunctionOrMethodContents')
-
-    param_scope = utils.get_field(func_contents, 'parameterScope')
-    utils.assert_interface(param_scope, 'AssertedParameterScope')
-    param_names = utils.get_field(param_scope, 'paramNames')
-
-    param_name = utils.get_element(param_names, 0)
-    utils.assert_interface(param_name, 'AssertedPositionalParameterName')
+    param_name = param_names.elem(0) \
+        .assert_interface('AssertedPositionalParameterName')
 
     for i in range(1, 65536 + 1):
-        copied_param_name = utils.copy_tagged_tuple(param_name)
-        index = utils.get_field(copied_param_name, "index")
-        name = utils.get_field(copied_param_name, "name")
+        copied_param_name = param_name.copy()
+
+        copied_param_name.field('index') \
+            .set_unsigned_long(i)
 
-        utils.set_unsigned_long(index, i)
-        utils.set_identifier_name(name, "a{}".format(i))
+        copied_param_name.field('name') \
+            .set_identifier_name('a{}'.format(i))
 
-        utils.append_element(param_names, copied_param_name)
+        param_names.append_elem(copied_param_name)
 
     return ast
--- a/js/src/jsapi-tests/binast/invalid/tests/param-unmatching-name.py
+++ b/js/src/jsapi-tests/binast/invalid/tests/param-unmatching-name.py
@@ -1,24 +1,20 @@
 def filter_ast(ast):
     # Set different parameter name than one in scope.
     import filter_utils as utils
 
-    utils.assert_interface(ast, 'Script')
-    global_stmts = utils.get_field(ast, 'statements')
-
-    func_stmt = utils.get_element(global_stmts, 0)
-    utils.assert_interface(func_stmt, 'EagerFunctionDeclaration')
-
-    func_contents = utils.get_field(func_stmt, 'contents')
-    utils.assert_interface(func_contents, 'FunctionOrMethodContents')
-
-    params = utils.get_field(func_contents, 'params')
-    utils.assert_interface(params, 'FormalParameters')
-    params_items = utils.get_field(params, 'items')
-
-    param = utils.get_element(params_items, 0)
-    utils.assert_interface(param, 'BindingIdentifier')
-
-    name = utils.get_field(param, "name")
-    utils.set_identifier_name(name, "b")
+    utils.wrap(ast) \
+        .assert_interface('Script') \
+        .field('statements') \
+        .elem(0) \
+        .assert_interface('EagerFunctionDeclaration') \
+        .field('contents') \
+        .assert_interface('FunctionOrMethodContents') \
+        .field('params') \
+        .assert_interface('FormalParameters') \
+        .field('items') \
+        .elem(0) \
+        .assert_interface('BindingIdentifier') \
+        .field('name') \
+        .set_identifier_name('b')
 
     return ast
--- a/js/src/jsapi-tests/binast/invalid/tests/wtf-8-field-name.py
+++ b/js/src/jsapi-tests/binast/invalid/tests/wtf-8-field-name.py
@@ -1,18 +1,16 @@
 def filter_ast(ast):
     # Put WTF-8 string into field name.
     # In multipart format, field name is not encoded into the file,
     # so this has no effect.
     import filter_utils as utils
 
-    utils.assert_interface(ast, 'Script')
-    global_stmts = utils.get_field(ast, 'statements')
+    expr_stmt = utils.wrap(ast) \
+        .assert_interface('Script') \
+        .field('statements') \
+        .elem(0) \
+        .assert_interface('ExpressionStatement') \
 
-    expr_stmt = utils.get_element(global_stmts, 0)
-    utils.assert_interface(expr_stmt, 'ExpressionStatement')
-
-    field = utils.get_field(expr_stmt, 'expression')
-
-    utils.append_field(expr_stmt, u'\uD83E_\uDD9D', field)
-    utils.remove_field(expr_stmt, 'expression')
+    expr_stmt.append_field(u'\uD83E_\uDD9D',
+                           expr_stmt.remove_field('expression'))
 
     return ast
--- a/js/src/jsapi-tests/binast/invalid/tests/wtf-8-interface-name.py
+++ b/js/src/jsapi-tests/binast/invalid/tests/wtf-8-interface-name.py
@@ -1,13 +1,12 @@
 def filter_ast(ast):
     # Put WTF-8 string into interface name.
     import filter_utils as utils
 
-    utils.assert_interface(ast, 'Script')
-    global_stmts = utils.get_field(ast, 'statements')
-
-    expr_stmt = utils.get_element(global_stmts, 0)
-    utils.assert_interface(expr_stmt, 'ExpressionStatement')
-
-    utils.set_interface_name(expr_stmt, u'\uD83E_\uDD9D')
+    utils.wrap(ast) \
+        .assert_interface('Script') \
+        .field('statements') \
+        .elem(0) \
+        .assert_interface('ExpressionStatement') \
+        .set_interface_name(u'\uD83E_\uDD9D')
 
     return ast
--- a/js/src/jsapi-tests/binast/invalid/tests/wtf-8-scope-name.py
+++ b/js/src/jsapi-tests/binast/invalid/tests/wtf-8-scope-name.py
@@ -1,18 +1,15 @@
 def filter_ast(ast):
     # Put WTF-8 string into scope name.
     import filter_utils as utils
 
-    utils.assert_interface(ast, 'Script')
-
-    scope = utils.get_field(ast, 'scope')
-    utils.assert_interface(scope, 'AssertedScriptGlobalScope')
-
-    names = utils.get_field(scope, 'declaredNames')
-
-    decl_name = utils.get_element(names, 0)
-    utils.assert_interface(decl_name, 'AssertedDeclaredName')
-
-    name = utils.get_field(decl_name, 'name')
-    utils.set_identifier_name(name, u'\uD83E_\uDD9D')
+    utils.wrap(ast) \
+        .assert_interface('Script') \
+        .field('scope') \
+        .assert_interface('AssertedScriptGlobalScope') \
+        .field('declaredNames') \
+        .elem(0) \
+        .assert_interface('AssertedDeclaredName') \
+        .field('name') \
+        .set_identifier_name(u'\uD83E_\uDD9D')
 
     return ast
--- a/js/src/jsapi-tests/binast/invalid/tests/wtf-8-variable-name.py
+++ b/js/src/jsapi-tests/binast/invalid/tests/wtf-8-variable-name.py
@@ -1,20 +1,17 @@
 def filter_ast(ast):
     # Put WTF-8 string into identifier name.
     import filter_utils as utils
 
-    utils.assert_interface(ast, 'Script')
-    global_stmts = utils.get_field(ast, 'statements')
-
-    expr_stmt = utils.get_element(global_stmts, 0)
-    utils.assert_interface(expr_stmt, 'ExpressionStatement')
-
-    assign_expr = utils.get_field(expr_stmt, 'expression')
-    utils.assert_interface(assign_expr, 'AssignmentExpression')
-
-    binding = utils.get_field(assign_expr, 'binding')
-    utils.assert_interface(binding, 'AssignmentTargetIdentifier')
-
-    name = utils.get_field(binding, 'name')
-    utils.set_identifier_name(name, u'\uD83E_\uDD9D')
+    utils.wrap(ast) \
+        .assert_interface('Script') \
+        .field('statements') \
+        .elem(0) \
+        .assert_interface('ExpressionStatement') \
+        .field('expression') \
+        .assert_interface('AssignmentExpression') \
+        .field('binding') \
+        .assert_interface('AssignmentTargetIdentifier') \
+        .field('name') \
+        .set_identifier_name(u'\uD83E_\uDD9D')
 
     return ast