/usr/share/pyshared/ninja_ide/tools/completion/analyzer.py is in ninja-ide 2.3-2.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 | # -*- coding: utf-8 -*-
#
# This file is part of NINJA-IDE (http://ninja-ide.org).
#
# NINJA-IDE is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# any later version.
#
# NINJA-IDE is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with NINJA-IDE; If not, see <http://www.gnu.org/licenses/>.
# DISCLAIMER ABOUT READING THIS CODE:
# We are not responsible for any kind of mental or emotional
# damage that may arise from reading this code.
import re
import ast
import _ast
from ninja_ide.tools.logger import NinjaLogger
from ninja_ide.tools.completion import model
logger = NinjaLogger('ninja_ide.tools.completion.analyzer')
MAX_THRESHOLD = 3
def expand_attribute(attribute):
parent_name = []
while attribute.__class__ is ast.Attribute:
parent_name.append(attribute.attr)
attribute = attribute.value
name = '.'.join(reversed(parent_name))
attribute_id = ''
if attribute.__class__ is ast.Name:
attribute_id = attribute.id
elif attribute.__class__ is ast.Call:
if attribute.func.__class__ is ast.Attribute:
attribute_id = '%s.%s()' % (
expand_attribute(attribute.func.value),
attribute.func.attr)
else:
attribute_id = '%s()' % attribute.func.id
name = attribute_id if name == '' else ("%s.%s" % (attribute_id, name))
return name
class Analyzer(object):
__mapping = {
_ast.Tuple: '__builtin__.tuple',
_ast.List: '__builtin__.list',
_ast.ListComp: '__builtin__.list',
_ast.Str: '__builtin__.str',
_ast.Dict: '__builtin__.dict',
_ast.Num: '__builtin__.int',
'_ast.Float': '__builtin__.float',
'_ast.Bool': '__builtin__.bool',
_ast.Call: model.late_resolution,
_ast.Name: model.late_resolution,
_ast.Attribute: model.late_resolution,
}
def __init__(self):
self._fixed_line = -1
self.content = None
# self._functions = {}
def _get_valid_module(self, source, retry=0):
"""Try to parse the module and fix some errors if it has some."""
astModule = None
try:
astModule = ast.parse(source)
except SyntaxError as reason:
line = reason.lineno - 1
if line != self._fixed_line and reason.text is not None:
self._fixed_line = line
new_line = ''
#This is failing sometimes, it should remaing commented
#until we find the proper fix.
indent = re.match('^\s+', str(reason.text))
if indent is not None:
new_line = indent.group() + 'pass'
split_source = source.splitlines()
split_source[line] = new_line
source = '\n'.join(split_source)
if retry < MAX_THRESHOLD:
astModule = self._get_valid_module(source, retry + 1)
return astModule
def analyze(self, source, old_module=None):
"""Analyze the source provided and create the proper structure."""
astModule = self._get_valid_module(source)
if astModule is None:
return model.Module()
self.content = source.split('\n')
module = model.Module()
for symbol in astModule.body:
if symbol.__class__ is ast.Assign:
assigns = self._process_assign(symbol)[0]
module.add_attributes(assigns)
elif symbol.__class__ in (ast.Import, ast.ImportFrom):
module.add_imports(self._process_import(symbol))
elif symbol.__class__ is ast.ClassDef:
module.add_class(self._process_class(symbol))
elif symbol.__class__ is ast.FunctionDef:
module.add_function(self._process_function(symbol))
# elif symbol.__class__ is ast.Expr:
# self._process_expression(symbol.value)
if old_module is not None:
self._resolve_module(module, old_module)
self.content = None
# self._functions = {}
return module
def _resolve_module(self, module, old_module):
module.update_classes(old_module.classes)
module.update_functions(old_module.functions)
module.update_attributes(old_module.attributes)
def _assign_disambiguation(self, type_name, line_content):
"""Provide a specific builtin for the cases were ast doesn't work."""
line = line_content.split('=')
if len(line) < 2:
logger.error('_assign_disambiguation, line not valid: %r' %
line_content)
return type_name
value = line[1].strip()
# TODO: We have to analyze when the assign is: x,y = 1, 2
if type_name is _ast.Num and '.' in value:
type_name = '_ast.Float'
elif value in ('True', 'False'):
type_name = '_ast.Bool'
elif value == 'None':
type_name = None
return type_name
# def _process_expression(self, expr):
# """Process expression, not assignment."""
# if expr.__class__ is not ast.Call:
# return
# args = expr.args
# keywords = expr.keywords
# ar = []
# kw = {}
# for arg in args:
# type_value = arg.__class__
# arg_name = ''
# if type_value is ast.Call:
# arg_name = expand_attribute(arg.func)
# elif type_value is ast.Attribute:
# arg_name = expand_attribute(arg.attr)
# data_type = self.__mapping.get(type_value, model.late_resolution)
# ar.append((arg_name, data_type))
# for key in keywords:
# type_value = key.value.__class__
# data_type = self.__mapping.get(type_value, model.late_resolution)
# kw[key.arg] = data_type
# if expr.func.__class__ is ast.Attribute:
# name = expand_attribute(expr.func)
# else:
# name = expr.func.id
# self._functions[name] = (ar, kw)
def _process_assign(self, symbol):
"""Process an ast.Assign object to extract the proper info."""
assigns = []
attributes = []
for var in symbol.targets:
type_value = symbol.value.__class__
line_content = self.content[symbol.lineno - 1]
if type_value in (_ast.Num, _ast.Name):
type_value = self._assign_disambiguation(
type_value, line_content)
if type_value is None:
continue
data_type = self.__mapping.get(type_value, model.late_resolution)
if var.__class__ == ast.Attribute:
data = (var.attr, symbol.lineno, data_type, line_content,
type_value)
attributes.append(data)
elif var.__class__ == ast.Name:
data = (var.id, symbol.lineno, data_type, line_content,
type_value)
assigns.append(data)
# if type_value is ast.Call:
# self._process_expression(symbol.value)
return (assigns, attributes)
def _process_import(self, symbol):
"""Process an ast.Import and ast.ImportFrom object to extract data."""
imports = []
for imp in symbol.names:
if symbol.__class__ is ast.ImportFrom:
module_name = "%s.%s" % (symbol.module, imp.name)
else:
module_name = imp.name
name = imp.asname
if name is None:
name = imp.name
imports.append((name, module_name))
return imports
def _process_class(self, symbol):
"""Process an ast.ClassDef object to extract data."""
clazz = model.Clazz(symbol.name)
for base in symbol.bases:
if base == 'object':
continue
name = expand_attribute(base)
clazz.add_parent(name)
#TODO: Decotator
# for decorator in symbol.decorator_list:
# clazz.decorators.append(decorator.id)
# PARSE FUNCTIONS AND ATTRIBUTES
for sym in symbol.body:
if sym.__class__ is ast.Assign:
assigns = self._process_assign(sym)[0]
clazz.add_attributes(assigns)
elif sym.__class__ is ast.FunctionDef:
clazz.add_function(self._process_function(sym, clazz))
clazz.update_bases()
clazz.update_with_parent_data()
return clazz
def _process_function(self, symbol, parent=None):
"""Process an ast.FunctionDef object to extract data."""
function = model.Function(symbol.name)
#TODO: Decorators
#We are not going to collect data from decorators yet.
# for decorator in symbol.decorator_list:
#Decorators can be: Name, Call, Attributes
# function.decorators.append(decorator.id)
if symbol.args.vararg is not None:
assign = model.Assign(symbol.args.vararg)
assign.add_data(symbol.lineno, '__builtin__.list', None, None)
function.args[assign.name] = assign
if symbol.args.kwarg is not None:
assign = model.Assign(symbol.args.kwarg)
assign.add_data(symbol.lineno, '__builtin__.dict', None, None)
function.args[assign.name] = assign
#We store the arguments to compare with default backwards
defaults = []
for value in symbol.args.defaults:
#TODO: In some cases we can have something like: a=os.path
type_value = value.__class__
data_type = self.__mapping.get(type_value, None)
defaults.append((data_type, type_value))
for arg in reversed(symbol.args.args):
if isinstance(arg, ast.Tuple):
self._parse_tuple_in_func_arg(arg, function, symbol.lineno)
continue
elif arg.id == 'self':
continue
assign = model.Assign(arg.id)
data_type = (model.late_resolution, None)
if defaults:
data_type = defaults.pop()
assign.add_data(symbol.lineno, data_type[0], None, data_type[1])
function.args[assign.name] = assign
for sym in symbol.body:
if sym.__class__ is ast.Assign:
result = self._process_assign(sym)
function.add_attributes(result[0])
if parent is not None:
parent.add_attributes(result[1])
elif sym.__class__ is ast.FunctionDef:
function.add_function(self._process_function(sym))
if sym.__class__ is not ast.Assign:
self._search_recursive_for_types(function, sym, parent)
return function
def _parse_tuple_in_func_arg(self, symbol_tuple, function, lineno=0):
"""Parse the tuple inside a function argument call."""
for item in symbol_tuple.elts:
assign = model.Assign(item.id)
data_type = (model.late_resolution, None)
assign.add_data(lineno, data_type[0], None, data_type[1])
function.args[assign.name] = assign
def _search_recursive_for_types(self, function, symbol, parent=None):
"""Search for return recursively inside the function."""
if symbol.__class__ is ast.Assign:
result = self._process_assign(symbol)
function.add_attributes(result[0])
if parent is not None:
parent.add_attributes(result[1])
elif symbol.__class__ is ast.Return:
type_value = symbol.value.__class__
lineno = symbol.lineno
data_type = self.__mapping.get(type_value, None)
line_content = self.content[lineno - 1]
if data_type != model.late_resolution:
type_value = None
function.add_return(lineno, data_type, line_content, type_value)
elif symbol.__class__ in (ast.If, ast.For, ast.TryExcept):
for sym in symbol.body:
self._search_recursive_for_types(function, sym, parent)
for else_item in symbol.orelse:
self._search_recursive_for_types(function, else_item, parent)
elif symbol.__class__ is ast.TryFinally:
for sym in symbol.body:
self._search_recursive_for_types(function, sym, parent)
for else_item in symbol.finalbody:
self._search_recursive_for_types(function, else_item, parent)
# elif symbol.__class__ is ast.Expr:
# self._process_expression(symbol.value)
|