
    iiD                    x   U d Z ddlm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 ej                  j                  d e e
e      j"                  j"                                G d de      Z G d d	e      Z G d
 de      Z ej*                  d      gZded<    ej*                  d      Z ej*                  d      Z ej*                  d      Z ej*                  d      Z ej*                  d       ej*                  d       ej*                  d      gZded<    ej*                  d      Z ej*                  d      Zd*dZd+dZ d,dZ! ej*                  d      Z"d,dZ#	 	 	 	 d-dZ$d.dZ%d,d Z&d/d0d!Z'd1d"Z(d2d#Z)d3d4d$Z*d5d%Z+d5d&Z,d6d'Z-d7d8d(Z.e/d)k(  r ej`                   e.              yy)9u=  
code-review.py

로컬 패턴 매칭 기반 AI 코드리뷰 (LLM 불필요).
Greptile 대체 구현.

기능:
- 하드코딩된 시크릿 탐지 (API 키, 토큰, 비밀번호)
- TODO/FIXME 잔존 탐지
- 미사용 import 탐지 (Python)
- 과도한 함수 길이 탐지 (>50줄)
- git diff 기반 변경 파일 분석
- JSON 출력

Usage:
    python3 code-review.py
    python3 code-review.py --diff-range HEAD~3..HEAD
    python3 code-review.py --files scripts/foo.py scripts/bar.py
    python3 code-review.py --format json
    python3 code-review.py --format summary
    )annotationsN)Path)	TypedDictc                  @    e Zd ZU ded<   ded<   ded<   ded<   ded<   y)	FindingstrfileintlineseveritycategorymessageN__name__
__module____qualname____annotations__     */home/jay/workspace/scripts/code-review.pyr   r   +   s    
I
IMMLr   r   c                  ,    e Zd ZU ded<   ded<   ded<   y)ReviewSummaryr
   criticalwarninginfoNr   r   r   r   r   r   3   s    ML
Ir   r   c                  ,    e Zd ZU ded<   ded<   ded<   y)ReviewResultr
   files_analyzedlist[Finding]findingsr   summaryNr   r   r   r   r   r   9   s    r   r   a5  (?ix)
        (?:
            api[_\-]?key | secret[_\-]?key | access[_\-]?key | private[_\-]?key |
            auth[_\-]?token | bearer[_\-]?token | password | passwd | pwd |
            token | secret | api[_\-]?secret
        )
        \s*=\s*
        (?P<quote>['"]) (?P<val>[^'"]{4,}) (?P=quote)
        zlist[re.Pattern[str]]_SECRET_VAR_PATTERNSz(AKIA[0-9A-Z]{16})z;Bearer\s+(?P<tok>[A-Za-z0-9\-_]{20,}(?:\.[A-Za-z0-9\-_]+)*)z-----BEGIN\s+[A-Z ]+KEY-----z;(?:postgresql|mysql|mongodb|redis)://[^/\s'"@]+:[^/\s'"@]+@zos\.environz
os\.getenvzgetenv\(_ENV_REF_PATTERNSz	<[A-Z_]+>z^['"]?\s*['"]?$c                4     t         fdt        D              S )u'   줄이 환경변수 참조인지 확인c              3  @   K   | ]  }|j                          y wN)search).0pr   s     r   	<genexpr>z$_is_env_reference.<locals>.<genexpr>l   s     9!qxx~9s   )anyr#   )r   s   `r   _is_env_referencer,   j   s    9'8999r   c                >    t        t        j                  |             S )u&   값이 플레이스홀더인지 확인)bool_PLACEHOLDER_PATTERNr'   )values    r   _is_placeholderr1   o   s    $++E233r   c                   g }|s|S |j                         }t        |d      D ]  \  }}t        |      rt        D ]  }|j	                  |      }|s|j                  d      }|r|j                         rt        |      rF|j                  t        | |ddt        |      dkD  r	d|dd  d	nd| d
              n t        j	                  |      }	|	r2|j                  t        | |ddd|	j                  d       d
             t        j	                  |      }
|
r7|
j                  d      }|j                  t        | |ddd|dd  d             t        j	                  |      r|j                  t        | |ddd             t        j	                  |      s~|j                  t        | |ddd              |S )u   
    하드코딩된 시크릿 탐지.

    Args:
        filepath: 파일 경로
        content: 파일 내용

    Returns:
        Finding 리스트
       startvalr   hardcoded_secret   z"Possible hardcoded secret found: 'Nz...' 'r	   r   r   r   r   z Possible AWS Access Key found: 'tokzPossible Bearer token found: 'z...'zPEM private key header foundz,Database URL with embedded credentials found)
splitlines	enumerater,   r"   r'   groupstripr1   appendr   len_AWS_KEY_PATTERN_BEARER_PATTERN_PEM_PATTERN_DB_URL_PATTERN)filepathcontentr    lineslinenor   patternmr6   m_awsm_bearerr;   s               r   check_hardcoded_secretsrN   t   s    !H E!%q1 MT" , 	Gt$Aggen#))+1E%#!+!3  #3x"} ASb
%P#EcU!!L
 )	. !''-OO!'/>u{{1~>NaP #))$/..'COO!'/<S"XJdK t$OO!'/: !!$'OO!'/JKM^ Or   z4(?i)#.*?\b(TODO|FIXME|HACK|XXX)\b\s*:?\s*(?P<msg>.*)c                d   g }|s|S |j                         }t        |d      D ]  \  }}t        j                  |      }|s|j	                  d      j                         }|j	                  d      j                         }|r| d| n|}	|j                  t        | |ddd|	 d	              |S )
u   
    TODO/FIXME/HACK/XXX 잔존 탐지.

    Args:
        filepath: 파일 경로
        content: 파일 내용

    Returns:
        Finding 리스트
    r3   r4   msgz: r   todozTODO found: 'r9   r:   )	r<   r=   _TODO_PATTERNr'   r>   upperr?   r@   r   )
rF   rG   r    rH   rI   r   rK   keywordmsg_textdisplays
             r   check_todosrW      s     !H E!%q1   &ggaj&&(Gwwu~++-H2:	H:.GOO!##+G9A6 Or   c                F   g }t        j                  |       D ]  }t        |t         j                        rp|j                  D ]`  }|j
                  r|j
                  n|j                  j                  d      d   }|j                  |j                  |j                  |f       b t        |t         j                        s|j                  D ]N  }|j
                  r|j
                  n|j                  }|j                  |j                  |j                  |f       P  |S )u   
    AST에서 import 정보 추출.

    Returns:
        (lineno, module_name, alias) 튜플 리스트
        alias: import 시 사용되는 이름 (import X as Y → Y, from X import Y → Y)
    .r   )astwalk
isinstanceImportnamesasnamenamesplitr@   rI   
ImportFrom)treeimportsnodealias	used_names        r   _extract_importsrh     s     +-G EdCJJ' E,1LLELLejj>N>Ns>STU>V	UZZCDE cnn- E,1LLELLejj	UZZCDEE Nr   c                   t               }t        j                  |       D ]  }t        |t        j                  t        j
                  f      s.t        |t        j                        r|j                  |j                         dt        |t        j
                        st        |j                  t        j                        s|j                  |j                  j                          |S )uN   
    import 줄을 제외한 코드에서 사용된 모든 이름 수집.
    )	setrZ   r[   r\   Name	Attributeaddidr0   )rc   import_linesr^   re   s       r   _get_all_names_in_coderp     s     eE )dSXXs}}56$)		$''"D#--0Z

CHH5U		$**--() Lr   c                ^   g }|s|S | j                  d      s|S 	 t        j                  |      }t	        |      }|s|S |D ch c]  \  }}}|
 }}}t        ||      }|D ]1  \  }}	}
|
|vs|j                  t        | |ddd|
 d|	 d             3 |S # t        $ r |cY S w xY wc c}}w )u   
    미사용 import 탐지 (Python 파일만).

    Args:
        filepath: 파일 경로
        content: 파일 내용

    Returns:
        Finding 리스트
    .pyr   unused_importzUnused import: 'z	' (from 'z')r:   )endswithrZ   parseSyntaxErrorrh   rp   r@   r   )rF   rG   r    rc   rd   rI   _ro   
used_namesmodule_namerg   s              r   check_unused_importsrz   )  s     !H U#yy! t$G/67|vq!F7L7'l;J*1 
&YJ&OO!&,.yk;-rR
 O-   8s   B B)B&%B&c                   g }|s|S | j                  d      s|S 	 t        j                  |      }t        j                  |      D ]  }t        |t        j                  t        j                  f      s.|j                  }|j                  |j                  n|}||z
  dz   }||kD  sb|j                  t        | |ddd|j                   d| d| d	              |S # t        $ r |cY S w xY w)
u   
    과도한 함수 길이 탐지 (Python 파일만, >max_lines).

    Args:
        filepath: 파일 경로
        content: 파일 내용
        max_lines: 허용 최대 줄 수 (이 값 초과 시 탐지)

    Returns:
        Finding 리스트
    rr   r3   r   long_functionz
Function 'z' is z lines long (max: )r:   )rt   rZ   ru   rv   r[   r\   FunctionDefAsyncFunctionDefrI   
end_linenor@   r   r`   )	rF   rG   	max_linesr    rc   re   
start_lineend_line
func_liness	            r   check_function_lengthr   \  s     !H U#yy!  dS__c.B.BCDJ*.//*Et:H!J.2JI%%'!*!0#-dii[j\I_`i_jjk!l  O'  s   C C,+C,c                   t        |       }|j                         sg S 	 |j                  dd      }g }|j                  t        | |             |j                  t        | |             |j                  t        | |             |j                  t        | |             |S # t        t        f$ r2 	 |j                  d      }d|v rg cY S n# t
        $ r g cY cY S w xY wY w xY w)u   
    단일 파일에 대해 모든 체커를 실행.

    Args:
        filepath: 파일 경로

    Returns:
        Finding 리스트
    zutf-8strict)encodingerrorszlatin-1)r    )r   exists	read_textUnicodeDecodeErrorPermissionError	ExceptionextendrN   rW   rz   r   )rF   pathrG   r    s       r   review_filer     s     >D;;=	
..'(.C !HOO+Hg>?OOK'23OO(7;<OO)(G<=O 0 	nnin8G 	 ! 	I	 !s;   B" "C#2C	C#C#CC#CC#"C#c                    g }| D ]  }|j                  t        |              t        t        d |D              t        d |D              t        d |D                    }t	        t        |       ||      S )u   
    여러 파일에 대해 리뷰를 실행하고 결과를 집계.

    Args:
        files: 파일 경로 리스트

    Returns:
        ReviewResult
    c              3  2   K   | ]  }|d    dk(  sd  yw)r   r   r3   Nr   r(   fs     r   r*   zreview_all.<locals>.<genexpr>  s     L1*0KQL   c              3  2   K   | ]  }|d    dk(  sd  yw)r   r   r3   Nr   r   s     r   r*   zreview_all.<locals>.<genexpr>  s     J!q}	/IAJr   c              3  2   K   | ]  }|d    dk(  sd  yw)r   r   r3   Nr   r   s     r   r*   zreview_all.<locals>.<genexpr>  s     DqAjMV,CDr   )r   r   r   )r   r    r!   )r   r   r   sumr   rA   )filesall_findingsrF   r!   s       r   
review_allr     s}     #%L 3K123 LLLJ|JJDLDDG 5z r   c                (   	 ddd| g}t        j                  |ddd      }|j                  j                         D cg c]#  }|j	                         s|j	                         % }}|S c c}w # t         j
                  t        t        f$ r g cY S w xY w)u   
    git diff 기반으로 변경된 파일 목록 반환.

    Args:
        diff_range: git diff 범위 (예: "HEAD", "HEAD~3..HEAD", "--cached")

    Returns:
        변경된 파일 경로 리스트 (git 실패 시 빈 리스트)
    gitdiffz--name-onlyT)capture_outputtextcheck)
subprocessrunstdoutr<   r?   CalledProcessErrorFileNotFoundErrorOSError)
diff_rangecmdresultr   r   s        r   get_changed_filesr     s    fmZ8	
 %+MM$<$<$>Lq!'')LL M))+<gF 	s(   ;A. A)A)%A. )A. . BBc                2    t        j                  | dd      S )u   JSON 형식으로 출력   F)indentensure_ascii)jsondumps)r   s    r   format_jsonr     s    ::fQU;;r   c                   d| d    d| d   d    d| d   d    d| d   d	    d
| d   d   | d   d   z   | d   d	   z    g}| d   rj|j                  d       |j                  d       | d   D ]@  }|j                  d|d   j                         dd|d    d|d    d|d    d|d    
       B dj                  |      S )u   요약 형식으로 출력zFiles analyzed: r   z
Critical: r!   r   z
Warning:  r   z
Info:     r   z
Total:    r     z	Findings:z  [r   8z] r	   :r   z (r   z) r   
)r@   rS   join)r   rH   r   s      r   format_summaryr     s-    6"2345
VI&z234
VI&y123
VI&v./0
VI&z2VI5Fy5QQTZ[dTeflTmmnoE jR[!
# 	xALL3q}224Q7r!F)Aai[PVWXYcWdVeeghijshtguvw	x99Ur   c                     t        j                  dt         j                  t              } | j	                         }|j                  dd d       |j                  dddd	
       | j                  dddgdd       | S )Nu8   로컬 패턴 매칭 기반 코드리뷰 (LLM 불필요))descriptionformatter_classepilogz--diff-rangeu;   git diff 범위 (예: HEAD~3..HEAD, --cached). 기본: HEAD)defaulthelpz--files+FILEu   직접 지정할 파일 경로)nargsmetavarr   z--formatr   r!   u   출력 형식 (기본: json))choicesr   r   )argparseArgumentParserRawDescriptionHelpFormatter__doc__add_mutually_exclusive_groupadd_argument)parserr>   s     r   _build_parserr     s    $$N <<F
 //1E	J  
 
-	   #+	   Mr   c                   t               }|j                  |       }|j                  rt        |j                        }n%|j                  r|j                  nd}t        |      }|s't        dt        j                         t        g       }nt        |      }|j                  dk(  rt        t        |             nt        t        |             |d   d   dkD  rdS dS )NHEADu!   분석할 파일이 없습니다.)r	   r!   r   r   r3   )r   
parse_argsr   listr   r   printsysstderrr   formatr   r   )argvr   argsr   r   r   s         r   mainr   (  s    _FT"D zzTZZ (,T__V
!*-1

CBE" {{inV$%k&!" y!*-118q8r   __main__)r   r   returnr.   )r0   r   r   r.   )rF   r   rG   r   r   r   )rc   
ast.Moduler   zlist[tuple[int, str, str]])rc   r   ro   zset[int]r   zset[str])2   )rF   r   rG   r   r   r
   r   r   )rF   r   r   r   )r   	list[str]r   r   )r   )r   r   r   r   )r   r   r   r   )r   zargparse.ArgumentParserr&   )r   zlist[str] | Noner   r
   )1r   
__future__r   r   rZ   r   rer   r   pathlibr   typingr   r   insertr   __file__parentr   r   r   compiler"   r   rB   rC   rD   rE   r#   r/   _EMPTY_STRING_PATTERNr,   r1   rN   rR   rW   rh   rp   rz   r   r   r   r   r   r   r   r   r   exitr   r   r   <module>r      s  , #  
  	  
   3tH~,,334 5i I 9  BJJ  
/ + 
 2::78  "**_` rzz=> "**_` BJJ!"BJJ !BJJ, (  "rzz"23  #

#9: :
4
_L 

VWN
.+f)b P@:<
,696 zCHHTV r   