mirror of
https://github.com/davidhalter/jedi.git
synced 2025-12-06 22:14:27 +08:00
Remove nodes_to_execute in favor of a function in parser_utils.
This commit is contained in:
@@ -16,6 +16,7 @@ import sys
|
|||||||
from jedi.parser.python import load_grammar
|
from jedi.parser.python import load_grammar
|
||||||
from jedi.parser.python import tree
|
from jedi.parser.python import tree
|
||||||
from jedi.parser.python import parse
|
from jedi.parser.python import parse
|
||||||
|
from jedi.parser_utils import get_executable_nodes
|
||||||
from jedi import debug
|
from jedi import debug
|
||||||
from jedi import settings
|
from jedi import settings
|
||||||
from jedi import common
|
from jedi import common
|
||||||
@@ -331,7 +332,7 @@ class Script(object):
|
|||||||
module_node = self._get_module_node()
|
module_node = self._get_module_node()
|
||||||
self._evaluator.analysis_modules = [module_node]
|
self._evaluator.analysis_modules = [module_node]
|
||||||
try:
|
try:
|
||||||
for node in module_node.nodes_to_execute():
|
for node in get_executable_nodes(module_node):
|
||||||
context = self._get_module().create_context(node)
|
context = self._get_module().create_context(node)
|
||||||
if node.type in ('funcdef', 'classdef'):
|
if node.type in ('funcdef', 'classdef'):
|
||||||
# TODO This is stupid, should be private
|
# TODO This is stupid, should be private
|
||||||
|
|||||||
@@ -450,7 +450,8 @@ class Evaluator(object):
|
|||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
if def_.type == 'expr_stmt' and name in def_.get_defined_names():
|
if def_.type == 'expr_stmt' and name in def_.get_defined_names():
|
||||||
return self.eval_statement(context, def_, name)
|
return self.eval_statement(context, def_, name)
|
||||||
elif def_.type == 'for_stmt':
|
elif def_.type == 'for_stmt' and \
|
||||||
|
name.start_pos < def_.children[1].end_pos:
|
||||||
container_types = self.eval_element(context, def_.children[3])
|
container_types = self.eval_element(context, def_.children[3])
|
||||||
cn = ContextualizedNode(context, def_.children[3])
|
cn = ContextualizedNode(context, def_.children[3])
|
||||||
for_types = iterable.py__iter__types(self, container_types, cn)
|
for_types = iterable.py__iter__types(self, container_types, cn)
|
||||||
|
|||||||
@@ -25,16 +25,11 @@ Any subclasses of :class:`Scope`, including :class:`Module` has an attribute
|
|||||||
[<ImportName: import os@1,0>]
|
[<ImportName: import os@1,0>]
|
||||||
|
|
||||||
See also :attr:`Scope.subscopes` and :attr:`Scope.statements`.
|
See also :attr:`Scope.subscopes` and :attr:`Scope.statements`.
|
||||||
|
|
||||||
For static analysis purposes there exists a method called
|
|
||||||
``nodes_to_execute`` on all nodes and leaves. It's documented in the static
|
|
||||||
anaylsis documentation.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from inspect import cleandoc
|
from inspect import cleandoc
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
import textwrap
|
import textwrap
|
||||||
import abc
|
|
||||||
|
|
||||||
from jedi._compatibility import (Python3Method, is_py3, utf8_repr,
|
from jedi._compatibility import (Python3Method, is_py3, utf8_repr,
|
||||||
literal_eval, unicode)
|
literal_eval, unicode)
|
||||||
@@ -143,10 +138,6 @@ class PythonMixin():
|
|||||||
# Default is not being a scope. Just inherit from Scope.
|
# Default is not being a scope. Just inherit from Scope.
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def nodes_to_execute(self, last_added=False):
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
@Python3Method
|
@Python3Method
|
||||||
def name_for_position(self, position):
|
def name_for_position(self, position):
|
||||||
for c in self.children:
|
for c in self.children:
|
||||||
@@ -256,10 +247,6 @@ class Name(_LeafWithoutNewlines):
|
|||||||
'comp_for', 'with_stmt') \
|
'comp_for', 'with_stmt') \
|
||||||
and self in stmt.get_defined_names()
|
and self in stmt.get_defined_names()
|
||||||
|
|
||||||
def nodes_to_execute(self, last_added=False):
|
|
||||||
if last_added is False:
|
|
||||||
yield self
|
|
||||||
|
|
||||||
|
|
||||||
class Literal(PythonLeaf):
|
class Literal(PythonLeaf):
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
@@ -431,13 +418,6 @@ class Module(Scope):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def nodes_to_execute(self, last_added=False):
|
|
||||||
# Yield itself, class needs to be executed for decorator checks.
|
|
||||||
result = []
|
|
||||||
for child in self.children:
|
|
||||||
result += child.nodes_to_execute()
|
|
||||||
return result
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def used_names(self):
|
def used_names(self):
|
||||||
if self._used_names is None:
|
if self._used_names is None:
|
||||||
@@ -464,13 +444,6 @@ class Decorator(PythonBaseNode):
|
|||||||
type = 'decorator'
|
type = 'decorator'
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
|
|
||||||
def nodes_to_execute(self, last_added=False):
|
|
||||||
if self.children[-2] == ')':
|
|
||||||
node = self.children[-3]
|
|
||||||
if node != '(':
|
|
||||||
return node.nodes_to_execute()
|
|
||||||
return []
|
|
||||||
|
|
||||||
|
|
||||||
class ClassOrFunc(Scope):
|
class ClassOrFunc(Scope):
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
@@ -528,34 +501,6 @@ class Class(ClassOrFunc):
|
|||||||
sub.get_call_signature(func_name=self.name), docstr)
|
sub.get_call_signature(func_name=self.name), docstr)
|
||||||
return docstr
|
return docstr
|
||||||
|
|
||||||
def nodes_to_execute(self, last_added=False):
|
|
||||||
# Yield itself, class needs to be executed for decorator checks.
|
|
||||||
yield self
|
|
||||||
# Super arguments.
|
|
||||||
arglist = self.get_super_arglist()
|
|
||||||
try:
|
|
||||||
children = arglist.children
|
|
||||||
except AttributeError:
|
|
||||||
if arglist is not None:
|
|
||||||
for node_to_execute in arglist.nodes_to_execute():
|
|
||||||
yield node_to_execute
|
|
||||||
else:
|
|
||||||
for argument in children:
|
|
||||||
if argument.type == 'argument':
|
|
||||||
# metaclass= or list comprehension or */**
|
|
||||||
raise NotImplementedError('Metaclasses not implemented')
|
|
||||||
else:
|
|
||||||
for node_to_execute in argument.nodes_to_execute():
|
|
||||||
yield node_to_execute
|
|
||||||
|
|
||||||
# care for the class suite:
|
|
||||||
for node in self.children[self.children.index(':'):]:
|
|
||||||
# This could be easier without the fast parser. But we need to find
|
|
||||||
# the position of the colon, because everything after it can be a
|
|
||||||
# part of the class, not just its suite.
|
|
||||||
for node_to_execute in node.nodes_to_execute():
|
|
||||||
yield node_to_execute
|
|
||||||
|
|
||||||
|
|
||||||
def _create_params(parent, argslist_list):
|
def _create_params(parent, argslist_list):
|
||||||
"""
|
"""
|
||||||
@@ -676,21 +621,6 @@ class Function(ClassOrFunc):
|
|||||||
docstr = self.raw_doc
|
docstr = self.raw_doc
|
||||||
return '%s\n\n%s' % (self.get_call_signature(), docstr)
|
return '%s\n\n%s' % (self.get_call_signature(), docstr)
|
||||||
|
|
||||||
def nodes_to_execute(self, last_added=False):
|
|
||||||
# Yield itself, functions needs to be executed for decorator checks.
|
|
||||||
yield self
|
|
||||||
for param in self.params:
|
|
||||||
if param.default is not None:
|
|
||||||
yield param.default
|
|
||||||
# care for the function suite:
|
|
||||||
for node in self.children[4:]:
|
|
||||||
# This could be easier without the fast parser. The fast parser
|
|
||||||
# allows that the 4th position is empty or that there's even a
|
|
||||||
# fifth element (another function/class). So just scan everything
|
|
||||||
# after colon.
|
|
||||||
for node_to_execute in node.nodes_to_execute():
|
|
||||||
yield node_to_execute
|
|
||||||
|
|
||||||
|
|
||||||
class Lambda(Function):
|
class Lambda(Function):
|
||||||
"""
|
"""
|
||||||
@@ -734,14 +664,6 @@ class Lambda(Function):
|
|||||||
def yields(self):
|
def yields(self):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def nodes_to_execute(self, last_added=False):
|
|
||||||
for param in self.params:
|
|
||||||
if param.default is not None:
|
|
||||||
yield param.default
|
|
||||||
# Care for the lambda test (last child):
|
|
||||||
for node_to_execute in self.children[-1].nodes_to_execute():
|
|
||||||
yield node_to_execute
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<%s@%s>" % (self.__class__.__name__, self.start_pos)
|
return "<%s@%s>" % (self.__class__.__name__, self.start_pos)
|
||||||
|
|
||||||
@@ -752,11 +674,6 @@ class Flow(PythonBaseNode):
|
|||||||
'try', 'except', 'finally', 'else', 'if', 'elif', 'with', 'for', 'while'
|
'try', 'except', 'finally', 'else', 'if', 'elif', 'with', 'for', 'while'
|
||||||
)
|
)
|
||||||
|
|
||||||
def nodes_to_execute(self, last_added=False):
|
|
||||||
for child in self.children:
|
|
||||||
for node_to_execute in child.nodes_to_execute():
|
|
||||||
yield node_to_execute
|
|
||||||
|
|
||||||
def get_branch_keyword(self, node):
|
def get_branch_keyword(self, node):
|
||||||
start_pos = node.start_pos
|
start_pos = node.start_pos
|
||||||
if not (self.start_pos < start_pos <= self.end_pos):
|
if not (self.start_pos < start_pos <= self.end_pos):
|
||||||
@@ -857,16 +774,6 @@ class TryStmt(Flow):
|
|||||||
elif node == 'except':
|
elif node == 'except':
|
||||||
yield None
|
yield None
|
||||||
|
|
||||||
def nodes_to_execute(self, last_added=False):
|
|
||||||
result = []
|
|
||||||
for child in self.children[2::3]:
|
|
||||||
result += child.nodes_to_execute()
|
|
||||||
for child in self.children[0::3]:
|
|
||||||
if child.type == 'except_clause':
|
|
||||||
# Add the test node and ignore the `as NAME` definition.
|
|
||||||
result += child.children[1].nodes_to_execute()
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
class WithStmt(Flow):
|
class WithStmt(Flow):
|
||||||
type = 'with_stmt'
|
type = 'with_stmt'
|
||||||
@@ -887,16 +794,6 @@ class WithStmt(Flow):
|
|||||||
if node.type == 'with_item':
|
if node.type == 'with_item':
|
||||||
return node.children[0]
|
return node.children[0]
|
||||||
|
|
||||||
def nodes_to_execute(self, last_added=False):
|
|
||||||
result = []
|
|
||||||
for child in self.children[1::2]:
|
|
||||||
if child.type == 'with_item':
|
|
||||||
# Just ignore the `as EXPR` part - at least for now, because
|
|
||||||
# most times it's just a name.
|
|
||||||
child = child.children[0]
|
|
||||||
result += child.nodes_to_execute()
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
class Import(PythonBaseNode):
|
class Import(PythonBaseNode):
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
@@ -919,14 +816,6 @@ class Import(PythonBaseNode):
|
|||||||
def is_star_import(self):
|
def is_star_import(self):
|
||||||
return self.children[-1] == '*'
|
return self.children[-1] == '*'
|
||||||
|
|
||||||
def nodes_to_execute(self, last_added=False):
|
|
||||||
"""
|
|
||||||
`nodes_to_execute` works a bit different for imports, because the names
|
|
||||||
itself cannot directly get resolved (except on itself).
|
|
||||||
"""
|
|
||||||
# TODO couldn't we return the names? Would be nicer.
|
|
||||||
return [self]
|
|
||||||
|
|
||||||
|
|
||||||
class ImportFrom(Import):
|
class ImportFrom(Import):
|
||||||
type = 'import_from'
|
type = 'import_from'
|
||||||
@@ -1070,12 +959,6 @@ class KeywordStatement(PythonBaseNode):
|
|||||||
def keyword(self):
|
def keyword(self):
|
||||||
return self.children[0].value
|
return self.children[0].value
|
||||||
|
|
||||||
def nodes_to_execute(self, last_added=False):
|
|
||||||
result = []
|
|
||||||
for child in self.children:
|
|
||||||
result += child.nodes_to_execute()
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
class AssertStmt(KeywordStatement):
|
class AssertStmt(KeywordStatement):
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
@@ -1093,13 +976,6 @@ class GlobalStmt(KeywordStatement):
|
|||||||
def get_global_names(self):
|
def get_global_names(self):
|
||||||
return self.children[1::2]
|
return self.children[1::2]
|
||||||
|
|
||||||
def nodes_to_execute(self, last_added=False):
|
|
||||||
"""
|
|
||||||
The global keyword allows to define any name. Even if it doesn't
|
|
||||||
exist.
|
|
||||||
"""
|
|
||||||
return []
|
|
||||||
|
|
||||||
|
|
||||||
class ReturnStmt(KeywordStatement):
|
class ReturnStmt(KeywordStatement):
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
@@ -1112,12 +988,6 @@ class YieldExpr(PythonBaseNode):
|
|||||||
def type(self):
|
def type(self):
|
||||||
return 'yield_expr'
|
return 'yield_expr'
|
||||||
|
|
||||||
def nodes_to_execute(self, last_added=False):
|
|
||||||
if len(self.children) > 1:
|
|
||||||
return self.children[1].nodes_to_execute()
|
|
||||||
else:
|
|
||||||
return []
|
|
||||||
|
|
||||||
|
|
||||||
def _defined_names(current):
|
def _defined_names(current):
|
||||||
"""
|
"""
|
||||||
@@ -1167,14 +1037,6 @@ class ExprStmt(PythonBaseNode, DocstringMixin):
|
|||||||
except IndexError:
|
except IndexError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def nodes_to_execute(self, last_added=False):
|
|
||||||
# I think evaluating the statement (and possibly returned arrays),
|
|
||||||
# should be enough for static analysis.
|
|
||||||
result = [self]
|
|
||||||
for child in self.children:
|
|
||||||
result += child.nodes_to_execute(last_added=True)
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
class Param(PythonBaseNode):
|
class Param(PythonBaseNode):
|
||||||
"""
|
"""
|
||||||
@@ -1273,16 +1135,3 @@ class CompFor(PythonBaseNode):
|
|||||||
|
|
||||||
def get_defined_names(self):
|
def get_defined_names(self):
|
||||||
return _defined_names(self.children[1])
|
return _defined_names(self.children[1])
|
||||||
|
|
||||||
def nodes_to_execute(self, last_added=False):
|
|
||||||
last = self.children[-1]
|
|
||||||
if last.type == 'comp_if':
|
|
||||||
for node in last.children[-1].nodes_to_execute():
|
|
||||||
yield node
|
|
||||||
last = self.children[-2]
|
|
||||||
elif last.type == 'comp_for':
|
|
||||||
for node in last.nodes_to_execute():
|
|
||||||
yield node
|
|
||||||
last = self.children[-2]
|
|
||||||
for node in last.nodes_to_execute():
|
|
||||||
yield node
|
|
||||||
|
|||||||
@@ -132,9 +132,6 @@ class Leaf(_NodeOrLeaf):
|
|||||||
else:
|
else:
|
||||||
return self.value
|
return self.value
|
||||||
|
|
||||||
def nodes_to_execute(self, last_added=False):
|
|
||||||
return []
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def end_pos(self):
|
def end_pos(self):
|
||||||
"""
|
"""
|
||||||
@@ -276,13 +273,6 @@ class Node(BaseNode):
|
|||||||
"""Concrete implementation for interior nodes."""
|
"""Concrete implementation for interior nodes."""
|
||||||
__slots__ = ('type',)
|
__slots__ = ('type',)
|
||||||
|
|
||||||
_IGNORE_EXECUTE_NODES = set([
|
|
||||||
'suite', 'subscriptlist', 'subscript', 'simple_stmt', 'sliceop',
|
|
||||||
'testlist_comp', 'dictorsetmaker', 'trailer', 'decorators',
|
|
||||||
'decorated', 'arglist', 'argument', 'exprlist', 'testlist',
|
|
||||||
'testlist_safe', 'testlist1'
|
|
||||||
])
|
|
||||||
|
|
||||||
def __init__(self, type, children):
|
def __init__(self, type, children):
|
||||||
"""
|
"""
|
||||||
Initializer.
|
Initializer.
|
||||||
@@ -295,19 +285,6 @@ class Node(BaseNode):
|
|||||||
super(Node, self).__init__(children)
|
super(Node, self).__init__(children)
|
||||||
self.type = type
|
self.type = type
|
||||||
|
|
||||||
def nodes_to_execute(self, last_added=False):
|
|
||||||
"""
|
|
||||||
For static analysis.
|
|
||||||
"""
|
|
||||||
result = []
|
|
||||||
if self.type not in Node._IGNORE_EXECUTE_NODES and not last_added:
|
|
||||||
result.append(self)
|
|
||||||
last_added = True
|
|
||||||
|
|
||||||
for child in self.children:
|
|
||||||
result += child.nodes_to_execute(last_added)
|
|
||||||
return result
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "%s(%s, %r)" % (self.__class__.__name__, self.type, self.children)
|
return "%s(%s, %r)" % (self.__class__.__name__, self.type, self.children)
|
||||||
|
|
||||||
@@ -319,9 +296,6 @@ class ErrorNode(BaseNode):
|
|||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
type = 'error_node'
|
type = 'error_node'
|
||||||
|
|
||||||
def nodes_to_execute(self, last_added=False):
|
|
||||||
return []
|
|
||||||
|
|
||||||
|
|
||||||
class ErrorLeaf(Leaf):
|
class ErrorLeaf(Leaf):
|
||||||
"""
|
"""
|
||||||
@@ -337,5 +311,3 @@ class ErrorLeaf(Leaf):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<%s: %s:%s, %s)>" % \
|
return "<%s: %s:%s, %s)>" % \
|
||||||
(type(self).__name__, self.original_type, repr(self.value), self.start_pos)
|
(type(self).__name__, self.original_type, repr(self.value), self.start_pos)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
76
jedi/parser_utils.py
Normal file
76
jedi/parser_utils.py
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
from jedi.parser.python.tree import PythonLeaf
|
||||||
|
_IGNORE_EXECUTE_NODES = set([
|
||||||
|
'suite', 'subscriptlist', 'subscript', 'simple_stmt', 'sliceop',
|
||||||
|
'testlist_comp', 'dictorsetmaker', 'trailer', 'decorators',
|
||||||
|
'decorated', 'arglist', 'argument', 'exprlist', 'testlist',
|
||||||
|
'testlist_safe', 'testlist1', 'global_stmt', 'file_input', 'for_stmt',
|
||||||
|
'while_stmt', 'if_stmt', 'try_stmt', 'with_stmt', 'comp_for', 'comp_if',
|
||||||
|
'param', 'except_clause', 'dotted_name', 'keyword_stmt', 'return_stmt',
|
||||||
|
'del_stmt', 'pass_stmt', 'nonlocal_stmt', 'assert_stmt', 'break_stmt',
|
||||||
|
'continue_stmt', 'raise_stmt', 'yield_stmt'
|
||||||
|
])
|
||||||
|
|
||||||
|
return_ = 'import_name', 'import_from'
|
||||||
|
|
||||||
|
|
||||||
|
# last added: Flow, KeywordStatement
|
||||||
|
|
||||||
|
|
||||||
|
def get_executable_nodes(node, last_added=False):
|
||||||
|
"""
|
||||||
|
For static analysis.
|
||||||
|
"""
|
||||||
|
result = []
|
||||||
|
typ = node.type
|
||||||
|
if typ == 'classdef':
|
||||||
|
# Yield itself, class needs to be executed for decorator checks.
|
||||||
|
result.append(node)
|
||||||
|
# Super arguments.
|
||||||
|
arglist = node.get_super_arglist()
|
||||||
|
try:
|
||||||
|
children = arglist.children
|
||||||
|
except AttributeError:
|
||||||
|
if arglist is not None:
|
||||||
|
result += get_executable_nodes(arglist)
|
||||||
|
else:
|
||||||
|
for argument in children:
|
||||||
|
if argument.type == 'argument':
|
||||||
|
# metaclass= or list comprehension or */**
|
||||||
|
raise NotImplementedError('Metaclasses not implemented, yet.')
|
||||||
|
else:
|
||||||
|
result += get_executable_nodes(argument)
|
||||||
|
|
||||||
|
# Care for the class suite:
|
||||||
|
suite = node.children[-1]
|
||||||
|
result += get_executable_nodes(suite)
|
||||||
|
elif typ == 'yield_expr':
|
||||||
|
if len(node.children) > 1:
|
||||||
|
# TODO delete?
|
||||||
|
result += get_executable_nodes(node.children[1])
|
||||||
|
elif typ == 'name':
|
||||||
|
next_leaf = node.get_next_leaf()
|
||||||
|
if last_added is False and node.parent.type != 'param' and next_leaf != '=':
|
||||||
|
result.append(node)
|
||||||
|
elif typ == 'expr_stmt':
|
||||||
|
# I think evaluating the statement (and possibly returned arrays),
|
||||||
|
# should be enough for static analysis.
|
||||||
|
result.append(node)
|
||||||
|
for child in node.children:
|
||||||
|
result += get_executable_nodes(child, last_added=True)
|
||||||
|
elif isinstance(node, PythonLeaf):
|
||||||
|
pass
|
||||||
|
elif typ == 'decorator':
|
||||||
|
# decorator
|
||||||
|
if node.children[-2] == ')':
|
||||||
|
node = children[-3]
|
||||||
|
if node != '(':
|
||||||
|
result += get_executable_nodes(node)
|
||||||
|
else:
|
||||||
|
if node.type not in _IGNORE_EXECUTE_NODES and not last_added:
|
||||||
|
result.append(node)
|
||||||
|
#last_added = True
|
||||||
|
|
||||||
|
for child in node.children:
|
||||||
|
result += get_executable_nodes(child, last_added)
|
||||||
|
|
||||||
|
return result
|
||||||
@@ -17,7 +17,7 @@ tuple(a + 3 for a in [''])
|
|||||||
# Some variables within are not defined
|
# Some variables within are not defined
|
||||||
# ----------
|
# ----------
|
||||||
|
|
||||||
abcdef = None
|
abcdef = []
|
||||||
#! 12 name-error
|
#! 12 name-error
|
||||||
[1 for a in NOT_DEFINFED for b in abcdef if 1]
|
[1 for a in NOT_DEFINFED for b in abcdef if 1]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user