@@ -230,33 +230,47 @@ def add_definition(name, node_id):
230230 if name and isinstance (name , str ) and name .strip ():
231231 defined_names [name .strip ()] = node_id
232232
233- for block in blocks :
234- if not isinstance (block , dict ):
235- continue
236- b_type = block .get ('type' , '' )
237- if b_type == 'py_var' :
238- add_definition (block .get ('var_name' ), block .get ('id' ))
239- elif b_type == 'py_math' :
240- add_definition (block .get ('target' ), block .get ('id' ))
241- elif b_type in ['py_int' , 'py_float' , 'py_input' ]:
242- add_definition (block .get ('target' ), block .get ('id' ))
243- elif b_type == 'py_loop' :
244- add_definition (block .get ('iterator' ), block .get ('id' ))
245- elif b_type == 'py_func' :
246- add_definition (block .get ('func_name' ), block .get ('id' ))
247- elif b_type == 'py_class' :
248- add_definition (block .get ('name' ), block .get ('id' ))
233+ # PRIMERA PASADA (RECURSIVA): Registrar todas las variables en todos los niveles
234+ def gather_definitions (block_list ):
235+ for block in block_list :
236+ if not isinstance (block , dict ):
237+ continue
238+ b_type = block .get ('type' , '' )
239+ if b_type == 'py_var' :
240+ add_definition (block .get ('var_name' ), block .get ('id' ))
241+ elif b_type == 'py_math' :
242+ add_definition (block .get ('target' ), block .get ('id' ))
243+ elif b_type in ['py_int' , 'py_float' , 'py_input' ]:
244+ add_definition (block .get ('target' ), block .get ('id' ))
245+ elif b_type == 'py_loop' :
246+ add_definition (block .get ('iterator' ), block .get ('id' ))
247+ elif b_type == 'py_func' :
248+ add_definition (block .get ('func_name' ), block .get ('id' ))
249+ elif b_type == 'py_class' :
250+ add_definition (block .get ('name' ), block .get ('id' ))
251+
252+ # Si el nodo tiene hijos (If, For, etc), entramos a revisarlos
253+ body = block .get ('body' , [])
254+ if isinstance (body , list ):
255+ gather_definitions (body )
256+
257+ gather_definitions (blocks )
249258
250259 def suggest (name ):
251260 candidates = difflib .get_close_matches (name , defined_names .keys (), n = 1 , cutoff = 0.6 )
252261 return candidates [0 ] if candidates else None
253262
254263 def check_expression (expr , block_id , description ):
255- names , parse_error = _extract_names (expr )
264+ if expr is None :
265+ return
266+
267+ # str() protege contra números puros que puedan crashear el AST
268+ names , parse_error = _extract_names (str (expr ))
269+
256270 if parse_error :
257271 errors .append ({
258272 'node_id' : block_id ,
259- 'message' : f"Error de sintaxis en { description } : { parse_error } " ,
273+ 'message' : f"Sintaxis inválida en { description } " ,
260274 'suggestion' : None
261275 })
262276 return
@@ -268,55 +282,61 @@ def check_expression(expr, block_id, description):
268282 if suggestion :
269283 errors .append ({
270284 'node_id' : block_id ,
271- 'message' : f"Nombre no definido '{ name } ' en { description } . Quizás quiso decir '{ suggestion } '?" ,
285+ 'message' : f"'{ name } ' no existe. ¿Quisiste decir '{ suggestion } '?" ,
272286 'suggestion' : suggestion
273287 })
274288 else :
275289 errors .append ({
276290 'node_id' : block_id ,
277- 'message' : f"Nombre no definido '{ name } ' en { description } ." ,
291+ 'message' : f"La variable '{ name } ' no ha sido creada ." ,
278292 'suggestion' : None
279293 })
280294
281- for block in blocks :
282- if not isinstance (block , dict ):
283- continue
284- b_type = block .get ('type' , '' )
285- block_id = block .get ('id' )
286- if b_type == 'py_var' :
287- check_expression (block .get ('value' , '' ), block_id , 'valor de variable' )
288- elif b_type == 'py_math' :
289- check_expression (block .get ('left' , '' ), block_id , 'lado izquierdo de la operación' )
290- check_expression (block .get ('right' , '' ), block_id , 'lado derecho de la operación' )
291- elif b_type == 'py_print' :
292- check_expression (block .get ('content' , '' ), block_id , 'contenido de print' )
293- elif b_type == 'py_input' :
294- # target is definition, prompt can be ignored
295- pass
296- elif b_type == 'py_int' or b_type == 'py_float' :
297- check_expression (block .get ('value' , '' ), block_id , 'valor de conversión' )
298- elif b_type == 'py_compare' :
299- check_expression (block .get ('left' , '' ), block_id , 'lado izquierdo de la comparación' )
300- check_expression (block .get ('right' , '' ), block_id , 'lado derecho de la comparación' )
301- elif b_type == 'py_if' :
302- check_expression (block .get ('condition' , '' ), block_id , 'condición if' )
303- elif b_type == 'py_elif' :
304- check_expression (block .get ('condition' , '' ), block_id , 'condición elif' )
305- elif b_type == 'py_loop' :
306- check_expression (block .get ('iterable' , '' ), block_id , 'iterable del for' )
307- elif b_type == 'py_while' :
308- check_expression (block .get ('condition' , '' ), block_id , 'condición while' )
309- elif b_type == 'py_call' :
310- func_expr = block .get ('func' , '' )
311- args_expr = block .get ('args' , '' )
312- check_expression (func_expr , block_id , 'función llamada' )
313- if args_expr :
314- # parse as tuple-like args
315- check_expression (f"({ args_expr } )" , block_id , 'argumentos de la llamada' )
316- elif b_type == 'py_return' :
317- check_expression (block .get ('value' , '' ), block_id , 'valor de retorno' )
318-
319- # Remove duplicates by node_id + message
295+ # SEGUNDA PASADA (RECURSIVA): Buscar errores en cualquier profundidad
296+ def check_blocks (block_list ):
297+ for block in block_list :
298+ if not isinstance (block , dict ):
299+ continue
300+ b_type = block .get ('type' , '' )
301+ block_id = block .get ('id' )
302+
303+ if b_type == 'py_var' :
304+ check_expression (block .get ('value' , '' ), block_id , 'el valor' )
305+ elif b_type == 'py_math' :
306+ check_expression (block .get ('left' , '' ), block_id , 'el lado izquierdo' )
307+ check_expression (block .get ('right' , '' ), block_id , 'el lado derecho' )
308+ elif b_type == 'py_print' :
309+ check_expression (block .get ('content' , '' ), block_id , 'lo que se va a imprimir' )
310+ elif b_type in ['py_int' , 'py_float' ]:
311+ check_expression (block .get ('value' , '' ), block_id , 'la conversión' )
312+ elif b_type == 'py_compare' :
313+ check_expression (block .get ('left' , '' ), block_id , 'la comparación (izq)' )
314+ check_expression (block .get ('right' , '' ), block_id , 'la comparación (der)' )
315+ elif b_type == 'py_if' :
316+ check_expression (block .get ('condition' , '' ), block_id , 'la condición del If' )
317+ elif b_type == 'py_elif' :
318+ check_expression (block .get ('condition' , '' ), block_id , 'la condición del Elif' )
319+ elif b_type == 'py_loop' :
320+ check_expression (block .get ('iterable' , '' ), block_id , 'el rango del For' )
321+ elif b_type == 'py_while' :
322+ check_expression (block .get ('condition' , '' ), block_id , 'la condición del While' )
323+ elif b_type == 'py_call' :
324+ func_expr = block .get ('func' , '' )
325+ args_expr = block .get ('args' , '' )
326+ check_expression (func_expr , block_id , 'la llamada a función' )
327+ if args_expr :
328+ check_expression (f"({ args_expr } )" , block_id , 'los parámetros' )
329+ elif b_type == 'py_return' :
330+ check_expression (block .get ('value' , '' ), block_id , 'el valor a retornar' )
331+
332+ # Volvemos a bajar en cascada para revisar el interior de los contenedores
333+ body = block .get ('body' , [])
334+ if isinstance (body , list ):
335+ check_blocks (body )
336+
337+ check_blocks (blocks )
338+
339+ # Filtrar duplicados
320340 unique = {}
321341 for err in errors :
322342 key = (err ['node_id' ], err ['message' ])
0 commit comments