
    RiJ              
          d Z ddlZddlZddlZddlZddlZddlZddlmZ ddl	m
Z
 ddlmZmZmZmZmZ  ej"                  e      Zde
deej(                     fdZd	ej(                  d
edee   fdZd	ej(                  deeef   deeeef      fdZd	ej(                  ded
edee   fdZ G d d      Z	 dde
dee   dee   dee   fdZd Zedk(  r e        yy)u  
ast_dependency_map.py — AST 기반 Python 의존성 맵 생성기

주어진 변경 파일 목록에 대해 영향 범위(blast radius)를 분석합니다.
- 직접 임포터 (direct importers)
- 전이적 의존자 (transitive dependents, 최대 2홉)
- 테스트 파일 (test_*.py 또는 tests/ 디렉토리)
- 함수 레벨 호출자 (function-level callers)

Usage:
    python3 ast_dependency_map.py --root /home/jay/workspace/dashboard --files data_loader.py
    python3 ast_dependency_map.py --root /home/jay/workspace/dashboard --files data_loader.py --function get_member_status
    python3 ast_dependency_map.py --root /home/jay/workspace/dashboard --files wiki_engine.py --function sync_firestore
    N)defaultdict)Path)DictListOptionalSetTuple	file_pathreturnc                    	 | j                         j                  }|dkD  rt        j                  d|dz  |        y| j	                  dd      }t        j                  |t        |             S # t        $ r Y yt        $ r Y yw xY w)	uU   파일을 AST로 파싱합니다. 구문 오류가 있으면 None을 반환합니다.i u!   대형 파일 스킵 (%.1fKB): %si   Nzutf-8replace)encodingerrors)filename)
statst_sizeloggerwarning	read_textastparsestrSyntaxError	Exception)r
   	file_sizesources      K/home/jay/workspace/.worktrees/task-2117-dev1/scripts/ast_dependency_map.py_parse_filer   "   s    NN$,,	wNN>	D@PR[\$$gi$Hyy#i.99  s   9A/ 2A/ /	B:BBtreepkg_namec                    t               }t        j                  |       D ]  }t        |t        j                        r|j
                  D ]t  }|j                  }d|v rQ|j                  d      }|d   |k(  r#t        |      dkD  r|j                  |d          O|j                  |d          d|j                  |       v t        |t        j                        s|j                  xs d}|j                  }|dkD  rW|r'|j                  d      d   }	|j                  |	       |j
                  D ]  }|j                  |j                          5|s9|j                  d      }|d   |k(  r$t        |      dkD  r|j                  |d          vt        |      dk(  r|j                  |d          |j                  |d           |S )u~  
    AST에서 임포트된 모듈 이름을 추출합니다.

    다음 패턴을 처리합니다:
    - import X
    - from X import Y
    - from dashboard.X import Y  →  X
    - from .X import Y  →  X (상대 임포트)
    - try/except 블록 내의 임포트 패턴

    반환값: 해당 파일이 의존하는 모듈명 집합 (예: {"data_loader", "server_utils"})
    .r       )setr   walk
isinstanceImportnamesnamesplitlenadd
ImportFrommodulelevel)
r   r    modulesnodealiasr+   partsr0   r1   tops
             r   _extract_imported_modulesr7   2   s    G +.dCJJ' &zz$; JJsOEQx8+E
QE!H-  E"I.KK%& cnn-[[&BFJJEqy ,,s+A.CKK$ "& 0EJJ/0 "LL-EQx8+E
QE!H-UqE!H-  E!H-W+.Z N    imported_namesc                    g }t        j                  |       D ]  }t        |t         j                        s|j                  }t        |t         j
                        r7|j                  |v sS|j                  |j                  |j                  f       {t        |t         j                        s|j                  }||v s|j                  ||j                  f        |S )u   
    AST에서 특정 함수 호출을 찾습니다.

    imported_names: {함수명: 소스모듈명} 매핑
    반환값: [(함수명, 줄번호), ...]
    )r   r'   r(   CallfuncNameidappendlineno	Attributeattr)r   r9   callsr3   r<   r+   s         r   _extract_function_callsrD   q   s     $&E 6dCHH%99D$)77n,LL$''4;;!78D#--0yy>)LL$!456 Lr8   target_modulec                    t               }t        j                  |       D ]  }t        |t        j                        s|j
                  xs d}|j                  }|dkD  r|r|j                  d      d   nd}|}n6|j                  d      }	|	d   |k(  rt        |	      dkD  r|	d   }n	|	r|	d   nd}||k(  s|j                  D ]E  }
|j                  |
j                         |
j                  s+|j                  |
j                         G  |S )u   
    특정 모듈에서 임포트된 이름들을 추출합니다.

    target_module: 소스 모듈 이름 (예: "data_loader")
    반환값: 임포트된 이름 집합
    r%   r   r"   r#   )r&   r   r'   r(   r/   r0   r1   r,   r-   r*   r.   r+   asname)r   rE   r    r*   r3   r0   r1   base
normalizedr5   r4   s              r   #_extract_imported_names_from_modulerJ      s     eE 0dCNN+[[&BFJJE qy/5v||C(+2!
S)8x'CJN!&qJ-2qJ]*!ZZ 0EIIejj)||		%,,/0%0. Lr8   c            	           e Zd ZdZdefdZd Zdedee   fdZ	dede
e   fd	Zdded
ede
e   fdZdededeeeef      fdZdedefdZy)DependencyGraphu]   
    대상 디렉토리 내 Python 파일들의 의존성 그래프를 구축합니다.
    rootc                     || _         |j                  | _        i | _        i | _        i | _        t        t              | _        | j                          y N)
rM   r+   r    module_to_filefile_to_astfile_importsr   r&   module_importers_build)selfrM   s     r   __init__zDependencyGraph.__init__   sD    			 02=?246A#6Fr8   c           	      
   t        j                          }d}t        | j                  j                  d            }|D ]  }|j	                  | j                        }|j
                  }|| j                  vr|| j                  |<   H| j                  |   }t        |j                        t        |j                        k  s|| j                  |<    |D ]  }t        j                          |z
  |kD  rrt        j                  d|t        | j                        t        |             |D ]7  }|| j                  vsd| j                  |<   t               | j                  |<   9  n[t        |      }	|	| j                  |<   |	&t        |	| j                         }
|
| j                  |<   t               | j                  |<    | j                  j#                         D ]*  \  }}|D ]   }| j$                  |   j'                  |       " , y)u:   디렉토리를 스캔하고 그래프를 구축합니다.<   z*.pyu;   그래프 구축 타임아웃 (%ds): %d/%d 파일 처리됨N)timelistrM   rglobrelative_tostemrP   r-   r5   r   r   rQ   r&   rR   r   r7   r    itemsrS   r.   )rU   build_startBUILD_TIMEOUTpy_filespy_filerelmodule_nameexisting	remainingr   importedimportsmods                r   rT   zDependencyGraph._build   s   iik		/0   	?G%%dii0C((K$"5"553:##K0..{;w}}%HNN(;;7>D''4	?   	3Gyy{[(=8Q!(()M	 "* =I (8(886:((37:u)))4= w'D(,DW%4T4==I-5!!'*-0U!!'*+	30 !% 1 1 7 7 9 	8GW 8%%c*..w78	8r8   r   r   c                     |j                  d      r|dd }n|}|| j                  v r| j                  |   S | j                  |z  }|j                         r|S y)u   
        파일명 또는 모듈명으로 파일 경로를 반환합니다.
        예: "data_loader.py" 또는 "data_loader"
        .pyN)endswithrP   rM   exists)rU   r   rd   	candidates       r   get_file_pathzDependencyGraph.get_file_path   sh     U#"3B-K"K $---&&{33 II(	r8   rd   c                 \    t        | j                  j                  |t                           S )uE   해당 모듈을 직접 임포트하는 파일들을 반환합니다.)r&   rS   get)rU   rd   s     r   get_direct_importersz$DependencyGraph.get_direct_importers  s"    4((,,[#%@AAr8   hopsc                 @   t               }| j                  |      }|j                  |       t        |dz
        D ]`  }t               }|D ]M  }|j                  }| j                  |      }	|	D ])  }
|
|vs|j                  |
       |j                  |
       + O |}b |S )u   
        전이적 의존자를 최대 hops 단계까지 반환합니다.
        직접 임포터는 포함하지 않습니다.
        r#   )r&   rs   updateranger]   r.   )rU   rd   rt   visitedcurrent_level_
next_levelr
   file_module	importersimps              r   get_transitive_dependentsz)DependencyGraph.get_transitive_dependents  s    
 !U#'#<#<[#I}%tax 
	'A$'EJ* )	'nn 55kB	$ )C')"s+C()	) 'M
	' r8   function_namec                    g }| j                  |      }| j                  |d      }||z  }|D ]  }| j                  j                  |      }|!t	        ||| j
                        }	||	vr%||	D 
cg c]  }
|
j                  d      d    c}
vr	 t        |||i      }|D ]  \  }}|j                  ||f         |S c c}
w )uo   
        특정 모듈의 특정 함수를 호출하는 (파일, 줄번호) 목록을 반환합니다.
           rt   r"   r$   )	rs   r   rQ   rr   rJ   r    r,   rD   r?   )rU   rd   r   callersdirect_importersall_dependentsall_candidatesr
   r   r9   nrC   rz   r@   s                 r   get_function_callersz$DependencyGraph.get_function_callers)  s     +-44[A 77!7L)N:' 	4I##''	2D| A{TXTaTabN N2}guLvbcQWWUX\Z\M]Lv7v ,D=+2NOE" 4	6	6234	4$  Mws   /B>r
   c                     |j                   }|j                  d      r|j                  d      ry	 |j                  | j                        }|j
                  }d|dd v ry	 y# t        $ r Y yw xY w)u+   테스트 파일 여부를 확인합니다.test_rk   TtestsNr$   F)r+   
startswithrm   r\   rM   r5   
ValueError)rU   r
   r+   rc   r5   s        r   is_test_filezDependencyGraph.is_test_fileH  sy    ~~??7#e(<	''		2CIIE%*$ %   		s   .A" "	A.-A.N)r   )__name__
__module____qualname____doc__r   rV   rT   r   r   rp   r   rs   intr   r   r	   r   boolr    r8   r   rL   rL      s    T 08dc htn ,B BD	 BS  CPTI . C DQVW[]`W`QaLb >d t r8   rL   rM   changed_filesr   c                 H    t               g }|D ]  }t        j                         dz  }j                  |       t        |      j                  }j                  |      }j                  |      }|r|j                  |       j                  |d      }	|	|z
  }
t               }t               }|D ]6  }j                  |      r|j                  |       &|j                  |       8 |
D ]%  }j                  |      s|j                  |       ' g }|rj                  ||      }dt        dt        f fd||
z  |z  }t        |      }d|i}|r||d<   t        fd	|D              }t        fd
|
D              }t        fd|D              }t        fd|D              }|||||d|d<   t        j                         dz  |z
  }|dkD  r#t        d| d|ddt         j"                         t%        |d      |d<   |j'                  |        |S )u   
    변경된 파일들에 대한 의존성 분석을 수행합니다.

    반환값: 각 변경 파일에 대한 분석 결과 리스트
    i  r   r   pr   c                 n    	 t        | j                              S # t        $ r t        |       cY S w xY wrO   )r   r\   r   )r   rM   s    r   to_relzanalyze.<locals>.to_rel  s4    1==.// 1vs    44changed_filechanged_functionc              3   R   K   | ]  }j                  |      r |         y wrO   r   .0r   graphr   s     r   	<genexpr>zanalyze.<locals>.<genexpr>  s$     %gAQVQcQcdeQffQi%g   ''c              3   R   K   | ]  }j                  |      r |         y wrO   r   r   s     r   r   zanalyze.<locals>.<genexpr>  s$     faPUPbPbcdPeq	fr   c              3   .   K   | ]  } |        y wrO   r   )r   r   r   s     r   r   zanalyze.<locals>.<genexpr>  s     >aq	>s   c              3   >   K   | ]  \  }} |       d |   yw):Nr   )r   r   r@   r   s      r   r   zanalyze.<locals>.<genexpr>  s$     YFq	{!F84Ys   )r   r   transitive_dependents
test_filestotal_affectedblast_radiusi'  u$   ⚠️ 대형 파일 분석 경고: z (z.0fzms)fileanalysis_time_ms)rL   rY   rp   r   r]   rs   discardr   r&   r   r.   r   r   r-   sortedprintsysstderrroundr?   )rM   r   r   resultsr   file_start_msrd   r   target_pathr   r   r   non_test_directr   callers_with_linesall_affectedr   resultdirect_importers_reltransitive_reltest_files_relcallers_relelapsedr   r   s   `                      @@r   analyzer   ^  sZ    D!EG% M		d* 	L)<(-- ',&@&@&M )),7$$[1 %*$C$CKVW$C$X .1A A !$
%(U! 	'A!!!$q!##A&		' ' 	"A!!!$q!	"
 68!&!;!;K!W	d 	s 	 #35J"JZ"W\* L
 )6F%&  &%g9I%ggf3Hff>:>>YFXYY !5"%3(,"
~ ))+$}4V8bQTUXY`c`j`jk%*7A%6!"v[M^ Nr8   c                     t        j                  dt         j                  t              } | j	                  dt
        dd       | j	                  ddd	d
d       | j	                  dt
        d d       | j	                  ddd
d       | j	                  dddd       | j                         }t        |j                        j                         }|j                         sHt        t        j                  dd| i      t        j                         t        j                   d       |j#                         sHt        t        j                  dd| i      t        j                         t        j                   d       t%        ||j&                  |j(                        }t+        |      dk(  r|d   n|}|j                  rd }n|j,                  rdnd }t        t        j                  |d|             y )Nu)   AST 기반 Python 의존성 맵 생성기)descriptionformatter_classepilogz--rootr"   uY   분석 대상 Python 코드베이스 루트 디렉토리 (기본값: 현재 디렉토리))typedefaulthelpz--files+FILETu>   분석할 변경 파일 목록 (예: data_loader.py server.py))nargsmetavarrequiredr   z
--functionu8   함수 레벨 분석을 위한 함수명 (선택 사항)z--pretty
store_trueu-   들여쓰기된 JSON 출력 (기본값: True))actionr   r   z--jsonFu0   컴팩트 JSON 출력 (--pretty 오버라이드)erroru0   루트 디렉토리를 찾을 수 없습니다: r   r#   u/   루트 경로가 디렉토리가 아닙니다: )rM   r   r   r   r   )ensure_asciiindent)argparseArgumentParserRawDescriptionHelpFormatterr   add_argumentr   
parse_argsr   rM   resolvern   r   jsondumpsr   r   exitis_dirr   filesfunctionr-   pretty)parserargsrM   r   outputr   s         r   mainr     s   $$? <<F
 h	   M   G	   <	   ?	   D		?""$D;;=JJ#STXSY!Z[\	
 	;;=JJ#RSWRX!YZ[	
 	jjmmG w<1,WQZ'F yykkt	$**V%
?@r8   __main__rO   )r   r   r   r   loggingr   rY   collectionsr   pathlibr   typingr   r   r   r   r	   	getLoggerr   r   Moduler   r   r7   r   rD   rJ   rL   dictr   r   r   r8   r   <module>r      sK    
   
  #  3 3			8	$4 HSZZ$8  <CJJ <# <#c( <~#** d38n QUV[\_ad\dVeQf 2 cjj    X[  `cdg`h  Pd d^ $(]
]9] C=] 
$Z	]JDAN zF r8   