def test_utf8_bom(): tree = parso.parse(unicode_bom + 'a = 1') expr_stmt = tree.children[0] assert expr_stmt.start_pos == (1, 0) tree = parso.parse(unicode_bom + '\n') endmarker = tree.children[0] parts = list(endmarker._split_prefix()) assert [p.type for p in parts] == ['bom', 'newline', 'spacing'] assert [p.start_pos for p in parts] == [(1, 0), (1, 0), (2, 0)] assert [p.end_pos for p in parts] == [(1, 0), (2, 0), (2, 0)]
def test_utf8_bom(): unicode_bom = BOM_UTF8.decode('utf-8') module = parso.parse(unicode_bom) endmarker = module.children[0] assert endmarker.type == 'endmarker' assert unicode_bom == endmarker.prefix module = parso.parse(unicode_bom + 'foo = 1') expr_stmt = module.children[0] assert expr_stmt.type == 'expr_stmt' assert unicode_bom == expr_stmt.get_first_leaf().prefix
def test_one_line_function(each_version): module = parse('def x(): f.', version=each_version) assert module.children[0].type == 'funcdef' def_, name, parameters, colon, f = module.children[0].children assert f.type == 'error_node' module = parse('def x(a:', version=each_version) func = module.children[0] assert func.type == 'error_node' if each_version.startswith('2'): assert func.children[-1].value == 'a' else: assert func.children[-1] == ':'
def test_get_call_signature(code, call_signature): node = parse(code, version='3.5').children[0] if node.type == 'simple_stmt': node = node.children[0] assert parser_utils.get_call_signature(node) == call_signature assert parser_utils.get_doc_with_call_signature(node) == call_signature
def test_ellipsis_py2(each_py2_version): module = parse('[0][...]', version=each_py2_version, error_recovery=False) expr = module.children[0] trailer = expr.children[-1] subscript = trailer.children[1] assert subscript.type == 'subscript' assert [leaf.value for leaf in subscript.children] == ['.', '.', '.']
def _expand_typestr(type_str): """ Attempts to interpret the possible types in `type_str` """ # Check if alternative types are specified with 'or' if re.search('\\bor\\b', type_str): for t in type_str.split('or'): yield t.split('of')[0].strip() # Check if like "list of `type`" and set type to list elif re.search('\\bof\\b', type_str): yield type_str.split('of')[0] # Check if type has is a set of valid literal values eg: {'C', 'F', 'A'} elif type_str.startswith('{'): node = parse(type_str, version='3.6').children[0] if node.type == 'atom': for leaf in node.children[1].children: if leaf.type == 'number': if '.' in leaf.value: yield 'float' else: yield 'int' elif leaf.type == 'string': if 'b' in leaf.string_prefix.lower(): yield 'bytes' else: yield 'str' # Ignore everything else. # Otherwise just work with what we have. else: yield type_str
def test_round_trip(): code = dedent(''' def x(): """hahaha""" func''') assert parse(code).get_code() == code
def test_get_code(): """Use the same code that the parser also generates, to compare""" s = '''"""a docstring""" class SomeClass(object, mixin): def __init__(self): self.xy = 3.0 """statement docstr""" def some_method(self): return 1 def yield_method(self): while hasattr(self, 'xy'): yield True for x in [1, 2]: yield x def empty(self): pass class Empty: pass class WithDocstring: """class docstr""" pass def method_with_docstring(): """class docstr""" pass ''' assert parse(s).get_code() == s
def _split_comment_param_declaration(decl_text): """ Split decl_text on commas, but group generic expressions together. For example, given "foo, Bar[baz, biz]" we return ['foo', 'Bar[baz, biz]']. """ try: node = parse(decl_text, error_recovery=False).children[0] except ParserSyntaxError: debug.warning('Comment annotation is not valid Python: %s' % decl_text) return [] if node.type == 'name': return [node.get_code().strip()] params = [] try: children = node.children except AttributeError: return [] else: for child in children: if child.type in ['name', 'atom_expr', 'power']: params.append(child.get_code().strip()) return params
def test_dont_break_imports_without_namespaces(): """ The code checking for ``from __future__ import absolute_import`` shouldn't assume that all imports have non-``None`` namespaces. """ src = "from __future__ import absolute_import\nimport xyzzy" assert parse(src)._has_explicit_absolute_import()
def node(self, request): parsed = parse(dedent(request.param[0]), version='3.5') request.keywords['expected'] = request.param[1] child = parsed.children[0] if child.type == 'simple_stmt': child = child.children[0] return child
def test_end_pos_one_line(): parsed = parse(dedent(''' def testit(): a = "huhu" ''')) simple_stmt = next(parsed.iter_funcdefs()).get_suite().children[-1] string = simple_stmt.children[0].get_rhs() assert string.end_pos == (3, 14)
def test_basic_parsing(): """Validate the parsing features""" m = parse(code_basic_features) diff_code_assert( code_basic_features, m.get_code() )
def test_carriage_return_at_end(code, types): """ By adding an artificial newline this creates weird side effects for \r at the end of files that would normally be error leafs. """ tree = parse(code) assert tree.get_code() == code assert [c.type for c in tree.children] == types
def test_invalid_token(): module = parse('a + ? + b') error_node, q, plus_b, endmarker = module.children assert error_node.get_code() == 'a +' assert q.value == '?' assert q.type == 'error_leaf' assert plus_b.type == 'factor' assert plus_b.get_code() == ' + b'
def test_with_stmt(): module = parse('with x: f.\na') assert module.children[0].type == 'with_stmt' w, with_item, colon, f = module.children[0].children assert f.type == 'error_node' assert f.get_code(include_prefix=False) == 'f.' assert module.children[2].type == 'name'
def check_p(src, number_parsers_used, number_of_splits=None, number_of_misses=0): if number_of_splits is None: number_of_splits = number_parsers_used module_node = parse(src) assert src == module_node.get_code() return module_node
def test_end_pos_line(each_version): # jedi issue #150 s = "x()\nx( )\nx( )\nx ( )\n" module = parse(s, version=each_version) for i, simple_stmt in enumerate(module.children[:-1]): expr_stmt = simple_stmt.children[0] assert expr_stmt.end_pos == (i + 1, i + 3)
def mutate(self, node, index): """Modify the For loop to evaluate to None""" assert index == 0 assert isinstance(node, ForStmt) empty_list = parso.parse(' []') node.children[3] = empty_list return node
def test_end_pos_multi_line(): parsed = parse(dedent(''' def testit(): a = """huhu asdfasdf""" + "h" ''')) expr_stmt = next(parsed.iter_funcdefs()).get_suite().children[1].children[0] string_leaf = expr_stmt.get_rhs().children[0] assert string_leaf.end_pos == (4, 11)
def test_carriage_return_at_end(code, types): """ By adding an artificial newline this created weird side effects for \r at the end of files. """ tree = parse(code) assert tree.get_code() == code assert [c.type for c in tree.children] == types assert tree.end_pos == (len(code) + 1, 0)
def initialize(self, code): logging.debug('differ: initialize') try: del cache.parser_cache[self.grammar._hashed][None] except KeyError: pass self.lines = split_lines(code, keepends=True) self.module = parse(code, diff_cache=True, cache=True) return self.module
def test_user_statement_on_import(): """github #285""" s = "from datetime import (\n" \ " time)" for pos in [(2, 1), (2, 4)]: p = parse(s) stmt = parser_utils.get_statement_of_position(p, pos) assert isinstance(stmt, tree.Import) assert [n.value for n in stmt.get_defined_names()] == ['time']
def test_annotation_params(each_py3_version): func = parse('def x(foo: 3, bar: 4): pass', version=each_py3_version).children[0] param1, param2 = func.get_params() assert param1.default is None assert param1.annotation.value == '3' assert not param1.star_count assert param2.default is None assert param2.annotation.value == '4' assert not param2.star_count
def test_if_else(): module = parse('if x:\n f.\nelse:\n g(') if_stmt = module.children[0] if_, test, colon, suite1, else_, colon, suite2 = if_stmt.children f = suite1.children[1] assert f.type == 'error_node' assert f.children[0].value == 'f' assert f.children[1].value == '.' g = suite2.children[1] assert g.children[0].value == 'g' assert g.children[1].value == '('
def assert_params(param_string, version=None, **wanted_dct): source = dedent(''' def x(%s): pass ''') % param_string module = parse(source, version=version) funcdef = next(module.iter_funcdefs()) dct = dict((p.name.value, p.default and p.default.get_code()) for p in funcdef.get_params()) assert dct == wanted_dct assert module.get_code() == source
def test_carriage_return_splitting(): source = u(dedent(''' "string" class Foo(): pass ''')) source = source.replace('\n', '\r\n') module = parse(source) assert [n.value for lst in module.get_used_names().values() for n in lst] == ['Foo']
def test_hex_values_in_docstring(): source = r''' def foo(object): """ \xff """ return 1 ''' doc = parser_utils.clean_scope_docstring(next(parse(source).iter_funcdefs())) if is_py3: assert doc == '\xff' else: assert doc == u'�'
def test_carriage_return_splitting(): source = dedent(''' "string" class Foo(): pass ''') source = source.replace('\n', '\r\n') module = parse(source) assert [n.value for lst in module.get_used_names().values() for n in lst] == ['Foo']
def test_iter_funcdefs(): code = dedent(''' def normal(): ... async def asyn(): ... @dec def dec_normal(): ... @dec1 @dec2 async def dec_async(): ... def broken ''') module = parse(code, version='3.8') func_names = [f.name.value for f in module.iter_funcdefs()] assert func_names == ['normal', 'asyn', 'dec_normal', 'dec_async']
def body_elements_from_source(source): # getsource adds a new line at the end of the the function, we don't need # this body = parso.parse(source).children[0].children[-1] # parso is adding a new line as first element, not sure if this # happens always though if isinstance(body.children[0], parso.python.tree.Newline): body_elements = body.children[1:] else: body_elements = body.children return body_elements, body.start_pos[0] - 1
def __init__( self, code_text: str, include_prefix: bool = False, language_version: Optional[str] = None, ): super().__init__() self._program = parso.parse(code_text, version=language_version) paths_to_scopes = self._build_filesystem() # For quick (direct) access of files (O(1) vs O(n)). self._files: Dict[PurePosixPath, Scope] = dict(paths_to_scopes) # For walking the filesystem. self._filesystem: Filesystem = make_fs(paths_to_scopes) self._include_prefix = include_prefix
def test_inactive_cache(tmpdir, isolated_parso_cache): parser_cache.clear() test_subjects = "abcdef" for path in test_subjects: parse('somecode', cache=True, path=os.path.join(str(tmpdir), path)) raw_cache_path = isolated_parso_cache.joinpath(_VERSION_TAG) assert raw_cache_path.exists() dir_names = os.listdir(raw_cache_path) a_while_ago = time.time() - _CACHED_FILE_MAXIMUM_SURVIVAL old_paths = set() for dir_name in dir_names[:len(test_subjects) // 2]: # make certain number of paths old os.utime(raw_cache_path.joinpath(dir_name), (a_while_ago, a_while_ago)) old_paths.add(dir_name) # nothing should be cleared while the lock is on assert _get_cache_clear_lock_path().exists() _remove_cache_and_update_lock() # it shouldn't clear anything assert len(os.listdir(raw_cache_path)) == len(test_subjects) assert old_paths.issubset(os.listdir(raw_cache_path)) os.utime(_get_cache_clear_lock_path(), (a_while_ago, a_while_ago)) _remove_cache_and_update_lock() assert len(os.listdir(raw_cache_path)) == len(test_subjects) // 2 assert not old_paths.intersection(os.listdir(raw_cache_path))
def test_cache_limit(): def cache_size(): return sum(len(v) for v in parser_cache.values()) try: parser_cache.clear() future_node_cache_item = _NodeCacheItem('bla', [], change_time=time.time() + 10e6) old_node_cache_item = _NodeCacheItem('bla', [], change_time=time.time() - 10e4) parser_cache['some_hash_old'] = { '/path/%s' % i: old_node_cache_item for i in range(300) } parser_cache['some_hash_new'] = { '/path/%s' % i: future_node_cache_item for i in range(300) } assert cache_size() == 600 parse('somecode', cache=True, path='/path/somepath') assert cache_size() == 301 finally: parser_cache.clear()
def parse(code, omissions=None, ignore_exception=False, ignore_yield=False): m = parso.parse(code) results = [] if 'class' in code: results = parse_classdefs(m, ignore_exception=ignore_exception, ignore_yield=ignore_yield) results += parse_defs( m, omissions=omissions, ignore_exception=ignore_exception, ignore_yield=ignore_yield, ) return results
def split_pipestring(s, sep="!"): segments = [] tree = parso.parse(s) current_nodes = [] for c in tree.children: if isinstance(c, parso.python.tree.PythonErrorLeaf) and c.value == sep: segments.append(current_nodes) current_nodes = [] else: current_nodes.append(c) segments.append(current_nodes) return ["".join(node.get_code() for node in seg) for seg in segments]
def test_eval_dump_recovers_parent(): module = parse("lambda x, y: x + y") module2 = eval(module.dump()) assert module2.parent is None lambda_node = module2.children[0] assert lambda_node.parent is module2 assert module2.children[1].parent is module2 assert lambda_node.children[0].parent is lambda_node param_node = lambda_node.children[1] assert param_node.parent is lambda_node assert param_node.children[0].parent is param_node assert param_node.children[1].parent is param_node arith_expr_node = lambda_node.children[-1] assert arith_expr_node.parent is lambda_node assert arith_expr_node.children[0].parent is arith_expr_node
def _test(self, expected, code): """Parse some Python code and compare it with an expected result. Args: expected (dict[str, str]): The output that `code` should be transformed into. code (str): The raw Python code to test with. """ graph = parso.parse(code) pairs = explains_comment.NeedsComment._get_comment_pairs( # pylint: disable=protected-access graph ) self.assertEqual(expected, pairs)
def deltas_compute(source, path, coverage, mutations): ast = parso.parse(source) ignored = 0 for (index, node) in zip(itertools.count(0), node_iter(ast)): for root, new_node in mutate(node, index, mutations): if not interesting(new_node, coverage): ignored += 1 continue target = root.get_code() delta = diff(source, target, path) yield delta if ignored > 1: msg = "Ignored {} mutations from file at {}" msg += " because there is no associated coverage." log.trace(msg, ignored, path)
def preprocess(filename): """Transformations before futurize called.""" with open(filename, 'r') as fh: tree = parso.parse(fh.read(), version='2.7') if tree.children[0].type == 'endmarker': return process_div(tree, filename) process_inkeys(tree, filename) process_iterview(tree, filename) process_octal(tree, filename) with open(filename, 'w') as fh: fh.write(tree.get_code())
def main(): args = argparser.parse_args() for filename in args.filenames: print(filename) if filename.endswith('.coffee'): continue ## avoid overwrite with open(filename, 'r', encoding='utf8') as pyfile: py = pyfile.read() newline = pyfile.newlines if isinstance(newline, tuple): newline = newline[0] tree = parso.parse(py, version=args.python_version) dump_tree(tree) csname = os.path.splitext(filename)[0] + '.coffee' print('==>', csname) cs = convert_tree(tree) with open(csname, 'w', newline=newline, encoding='utf8') as csfile: csfile.write(cs)
def indexSourceCode(sourceCode, workingDirectory, astVisitorClient, isVerbose, sysPath=None): sourceFilePath = _virtualFilePath moduleNode = parso.parse(sourceCode) if (isVerbose): astVisitor = VerboseAstVisitor(astVisitorClient, sourceFilePath, sourceCode, sysPath) else: astVisitor = AstVisitor(astVisitorClient, sourceFilePath, sourceCode, sysPath) astVisitor.traverseNode(moduleNode)
def check_command_docstrings(cogs: dict) -> int: ret = 0 for pkg_name in cogs: pkg_folder = ROOT_PATH / pkg_name for file in pkg_folder.glob("**/*.py"): with file.open() as fp: tree = parso.parse(fp.read()) for node in _scan_recursively(tree.children, "async_funcdef", CONTAINERS): funcdef = node.children[-1] decorators = funcdef.get_decorators() ignore = False # DEP-WARN: use of private method for prefix_part in decorators[0].children[0]._split_prefix(): if (prefix_part.type == "comment" and prefix_part.value == "# geninfo-ignore: missing-docstring"): ignore = True for deco in decorators: maybe_name = deco.children[1] if maybe_name.type == "dotted_name": it = (n.value for n in maybe_name.children) # ignore first item (can either be `commands` or `groupname`) next(it, None) deco_name = "".join(it) elif maybe_name.type == "name": deco_name = maybe_name.value else: raise RuntimeError( "Unexpected type of decorator name.") if deco_name in {".command", ".group"}: break else: continue if funcdef.get_doc_node() is None: if not ignore: print( "\033[93m\033[1mWARNING:\033[0m " f"command `{funcdef.name.value}` misses help docstring!" ) ret = 1 elif ignore: print("\033[93m\033[1mWARNING:\033[0m " f"command `{funcdef.name.value}` has unused" " missing-docstring ignore comment!") ret = 1 return ret
def pyls_folding_range(document): program = document.source + '\n' lines = program.splitlines() tree = parso.parse(program) ranges = __compute_folding_ranges(tree, lines) results = [] for (start_line, end_line) in ranges: start_line -= 1 end_line -= 1 # If start/end character is not defined, then it defaults to the # corresponding line last character results.append({ 'startLine': start_line, 'endLine': end_line, }) return results
def test_quoted_strings(): string_tokens = [ 'u"test"', 'u"""test"""', 'U"""test"""', "u'''test'''", "U'''test'''", ] for s in string_tokens: module = parse('''a = %s\n''' % s) simple_stmt = module.children[0] expr_stmt = simple_stmt.children[0] assert len(expr_stmt.children) == 3 string_tok = expr_stmt.children[2] assert string_tok.type == 'string' assert string_tok.value == s
def test_current_node(self): """Get the given node assuming it defines its own prefix.""" graph = parso.parse( textwrap.dedent( """\ foo = [ blah, another, ] """ ) ) name_node = graph.get_first_leaf() self.assertEqual( name_node, node_seek.get_node_with_first_prefix(name_node), )
def test_nested_prefix(self): """Get the prefix of a node whose prefix is stored in one of its child nodes.""" graph = parso.parse( textwrap.dedent( """\ foo = [ blah, another, ] """ ) ) name_node = graph.get_first_leaf() self.assertEqual( name_node, node_seek.get_node_with_first_prefix(graph), )
def validate_syntax(self): try: self.code_runner = CodeRunner( self.code, flags=PyCF_ALLOW_TOP_LEVEL_AWAIT).compile() except SyntaxError: import parso r = parso.parse(self.code) errors = [] for error in parso.load_grammar().iter_errors(r): error_dict = dict(start_pos=error.start_pos, end_pos=error.end_pos, msg=error.message) errors.append(error_dict) self.dispose() return to_js(dict(valid=False, errors=errors)) return to_js(dict(valid=True))
def indexSourceFile(sourceFilePath, environmentDirectoryPath, workingDirectory, astVisitorClient, isVerbose): if isVerbose: print('INFO: Indexing source file "' + sourceFilePath + '".') sourceCode = '' with open(sourceFilePath, 'r', encoding='utf-8') as input: sourceCode = input.read() moduleNode = parso.parse(sourceCode) if (isVerbose): astVisitor = VerboseAstVisitor(astVisitorClient, sourceFilePath) else: astVisitor = AstVisitor(astVisitorClient, sourceFilePath) astVisitor.traverseNode(moduleNode)
def __init__(self, source, **definitions): if definitions is None: definitions = {} source = source.strip() self.definitions = definitions self.module = parse(source) self.markers = [] def get_leaf(line, column, of_type=None): r = self.module.children[0].get_leaf_for_position((line, column)) while of_type is not None and r.type != of_type: r = r.parent return r def parse_markers(node): if hasattr(node, '_split_prefix'): for x in node._split_prefix(): parse_markers(x) if hasattr(node, 'children'): for x in node.children: parse_markers(x) if node.type == 'comment': line, column = node.start_pos for match in re.finditer(r'\^(?P<value>[^\^]*)', node.value): name = match.groupdict()['value'].strip() d = definitions.get(name, {}) assert set(d.keys()) | {'of_type', 'marker_type'} == {'of_type', 'marker_type'} self.markers.append(dict( node=get_leaf(line - 1, column + match.start(), of_type=d.get('of_type')), marker_type=d.get('marker_type'), name=name, )) parse_markers(self.module) pattern_nodes = [x['node'] for x in self.markers if x['name'] == 'match' or x['name'] == ''] if len(pattern_nodes) != 1: raise InvalidASTPatternException("Found more than one match node. Match nodes are nodes with an empty name or with the explicit name 'match'") self.pattern = pattern_nodes[0] self.marker_type_by_id = {id(x['node']): x['marker_type'] for x in self.markers}
def mutate(context): """ :type context: Context :return: tuple: mutated source code, number of mutations performed """ try: result = parse(context.source, error_recovery=False) except Exception: print('Failed to parse %s. Internal error from parso follows.' % context.filename) print('----------------------------------') raise mutate_list_of_nodes(result, context=context) mutated_source = result.get_code().replace(' not not ', ' ') if context.number_of_performed_mutations: # Check that if we said we mutated the code, that it has actually changed assert context.source != mutated_source context.mutated_source = mutated_source return mutated_source, context.number_of_performed_mutations
def test_simple_prefix_splitting(string, tokens): tree = parso.parse(string) leaf = tree.children[0] assert leaf.type == 'endmarker' parsed_tokens = list(leaf._split_prefix()) start_pos = (1, 0) for pt, expected in zip_longest(parsed_tokens, tokens): assert pt.value == expected # Calculate the estimated end_pos if expected.endswith('\n'): end_pos = start_pos[0] + 1, 0 else: end_pos = start_pos[0], start_pos[1] + len(expected) + len(pt.spacing) #assert start_pos == pt.start_pos assert end_pos == pt.end_pos start_pos = end_pos
def run(package, context): """Parse `package` into parso nodes and add the entire module into `context`. Args: package (:class:`rez.packages_.DeveloperPackage`): The Rez package that will get parsed. context (:class:`.Context`): A data instance that will stored the parsed output. """ path = package.filepath if not path.endswith(".py"): return with open(path, "r") as handler: code = handler.read() context[lint_constant.PARSO_GRAPH] = parso.parse(code)
def test_make_import_from_definitions(monkeypatch): source = """ def x():\n pass \n class A:\n pass def some_function(): pass """ mock_fn = Mock() mock_fn.__name__ = 'some_function' mock_mod = Mock() mock_mod.__name__ = 'some.module' monkeypatch.setattr(inspect, 'getmodule', lambda _: mock_mod) assert (interact.make_import_from_definitions( parso.parse(source), mock_fn) == 'from some.module import x, A')
def test_append(self): """Add the @early() help function to a package definition which doesn't have one.""" original = 'name = "whatever"' code_block = textwrap.dedent(""" @early() def help(): return [["foo", "bar"], ["Some Existing", "stuff"]] """) expected = textwrap.dedent("""\ name = "whatever" @early() def help(): return [["foo", "bar"], ["Some Existing", "stuff"]] """) self._test(expected, original, parso.parse(code_block))
def check_package_end_user_data_statements(ctx: InfoGenMainCommand) -> bool: success = True for pkg_name, cog_info in ctx.cogs.items(): path = ROOT_PATH / pkg_name / "__init__.py" if not path.is_file(): raise RuntimeError("Folder `{pkg_name}` isn't a valid package.") with path.open(encoding="utf-8") as fp: source = fp.read() tree = parso.parse(source) for node in scan_recursively(tree.children, "name", CONTAINERS_WITHOUT_LOCALS): if node.value == "__red_end_user_data_statement__": break else: print( "\033[93m\033[1mWARNING:\033[0m " f"cog package `{pkg_name}` is missing end user data statement!" ) success = False return success
def test_if_stmt(): module = parse('if x: f.\nelse: g(') if_stmt = module.children[0] assert if_stmt.type == 'if_stmt' if_, test, colon, f = if_stmt.children assert f.type == 'error_node' assert f.children[0].value == 'f' assert f.children[1].value == '.' assert module.children[1].type == 'newline' assert module.children[1].value == '\n' assert module.children[2].type == 'error_leaf' assert module.children[2].value == 'else' assert module.children[3].type == 'error_leaf' assert module.children[3].value == ':' in_else_stmt = module.children[4] assert in_else_stmt.type == 'error_node' assert in_else_stmt.children[0].value == 'g' assert in_else_stmt.children[1].value == '('
def use_snippets(document, position): """ Determine if it's necessary to return snippets in code completions. This returns `False` if a completion is being requested on an import statement, `True` otherwise. """ line = position['line'] lines = document.source.split('\n', line) act_lines = [lines[line][:position['character']]] line -= 1 while line > -1: act_line = lines[line] if act_line.rstrip().endswith('\\'): act_lines.insert(0, act_line) line -= 1 else: break tokens = parso.parse('\n'.join(act_lines).split(';')[-1].strip()) return tokens.children[0].type not in _IMPORTS