@@ -22,6 +22,23 @@ const DIRECT_SHARED_IMPORT_RULES = [
2222] ;
2323
2424const ALIAS_RULE = { rule : "shared-alias-import-disallowed" , regex : / @ s h a r e d \/ / g, label : "rule:shared-alias-marker" } ;
25+ const NUMBER_UTIL_IMPORT_HINT = / f r o m \s + [ " ' ] [ ^ " ' ] * s h a r e d \/ u t i l s \/ n u m b e r U t i l s \. j s [ " ' ] / ;
26+
27+ const INLINE_HELPER_VARIANT_RULES = [
28+ { rule : "inline-helper-clone" , regex : / \( v a l u e \) \s * = > \s * N u m b e r \. i s F i n i t e / g, label : "rule:inline-arrow-number-is-finite" } ,
29+ { rule : "inline-helper-clone" , regex : / t y p e o f \s + v a l u e \s * = = = \s * ' o b j e c t ' \s * & & \s * v a l u e \s * ! = = \s * n u l l / g, label : "rule:inline-plain-object-check" }
30+ ] ;
31+
32+ const RENAMED_HELPER_FUNCTION_RULES = [
33+ { rule : "renamed-helper-clone" , regex : / f u n c t i o n \s + f i n i t e N u m b e r \s * \( / g, label : "rule:renamed-helper-finiteNumber" } ,
34+ { rule : "renamed-helper-clone" , regex : / f u n c t i o n \s + p o s i t i v e I n t \s * \( / g, label : "rule:renamed-helper-positiveInt" } ,
35+ { rule : "renamed-helper-clone" , regex : / f u n c t i o n \s + p l a i n O b j \s * \( / g, label : "rule:renamed-helper-plainObj" }
36+ ] ;
37+
38+ const DEEP_RELATIVE_TRAVERSAL_RULES = [
39+ { rule : "deep-relative-shared-traversal" , regex : / \. \. \/ \. \. \/ \. \. \/ \. \. \/ s r c \/ s h a r e d / g, label : "rule:deep-relative-src-shared-traversal" } ,
40+ { rule : "deep-relative-shared-traversal" , regex : / \. \. \/ \. \. \/ \. \. \/ s r c \/ \. \. \/ s h a r e d / g, label : "rule:relative-src-parent-shared-traversal" }
41+ ] ;
2542
2643async function pathExists ( targetPath ) {
2744 try {
@@ -86,9 +103,101 @@ function findViolations(fileContent, filePathFromRoot) {
86103 } ) ;
87104 }
88105
106+ for ( const check of INLINE_HELPER_VARIANT_RULES ) {
107+ const matches = fileContent . match ( check . regex ) || [ ] ;
108+ for ( const _match of matches ) {
109+ violations . push ( {
110+ file : filePathFromRoot ,
111+ type : check . rule ,
112+ match : check . label
113+ } ) ;
114+ }
115+ }
116+
117+ for ( const check of RENAMED_HELPER_FUNCTION_RULES ) {
118+ const matches = fileContent . match ( check . regex ) || [ ] ;
119+ for ( const _match of matches ) {
120+ violations . push ( {
121+ file : filePathFromRoot ,
122+ type : check . rule ,
123+ match : check . label
124+ } ) ;
125+ }
126+ }
127+
128+ for ( const check of DEEP_RELATIVE_TRAVERSAL_RULES ) {
129+ const matches = fileContent . match ( check . regex ) || [ ] ;
130+ for ( const _match of matches ) {
131+ violations . push ( {
132+ file : filePathFromRoot ,
133+ type : check . rule ,
134+ match : check . label
135+ } ) ;
136+ }
137+ }
138+
139+ const finiteMatches = [ ...fileContent . matchAll ( / N u m b e r \. i s F i n i t e \s * \( / g) ] ;
140+ for ( const finiteMatch of finiteMatches ) {
141+ const matchIndex = finiteMatch . index ?? - 1 ;
142+ if ( matchIndex < 0 ) continue ;
143+ const lineStart = fileContent . lastIndexOf ( "\n" , matchIndex ) + 1 ;
144+ const lineEndCandidate = fileContent . indexOf ( "\n" , matchIndex ) ;
145+ const lineEnd = lineEndCandidate === - 1 ? fileContent . length : lineEndCandidate ;
146+ const lineText = fileContent . slice ( lineStart , lineEnd ) ;
147+
148+ if ( NUMBER_UTIL_IMPORT_HINT . test ( lineText ) ) continue ;
149+
150+ violations . push ( {
151+ file : filePathFromRoot ,
152+ type : "inline-helper-clone" ,
153+ match : "rule:number-is-finite-usage"
154+ } ) ;
155+ }
156+
89157 return violations ;
90158}
91159
160+ function summarizeViolationLabels ( violations ) {
161+ const counts = new Map ( ) ;
162+ for ( const violation of violations ) {
163+ const next = ( counts . get ( violation . match ) || 0 ) + 1 ;
164+ counts . set ( violation . match , next ) ;
165+ }
166+ return [ ...counts . entries ( ) ] . sort ( ( [ a ] , [ b ] ) => a . localeCompare ( b ) ) ;
167+ }
168+
169+ function printGroupedViolations ( violations ) {
170+ const byType = new Map ( ) ;
171+ for ( const violation of violations ) {
172+ if ( ! byType . has ( violation . type ) ) byType . set ( violation . type , new Map ( ) ) ;
173+ const byFile = byType . get ( violation . type ) ;
174+ if ( ! byFile . has ( violation . file ) ) byFile . set ( violation . file , [ ] ) ;
175+ byFile . get ( violation . file ) . push ( violation ) ;
176+ }
177+
178+ const typeEntries = [ ...byType . entries ( ) ] . sort ( ( [ a ] , [ b ] ) => a . localeCompare ( b ) ) ;
179+ for ( const [ type , filesMap ] of typeEntries ) {
180+ console . error ( `TYPE: ${ type } ` ) ;
181+ const fileEntries = [ ...filesMap . entries ( ) ] . sort ( ( [ a ] , [ b ] ) => a . localeCompare ( b ) ) ;
182+ for ( const [ file , fileViolations ] of fileEntries ) {
183+ console . error ( ` FILE: ${ file } ` ) ;
184+ const summaries = summarizeViolationLabels ( fileViolations ) ;
185+ for ( const [ label , count ] of summaries ) {
186+ console . error ( ` MATCH: ${ label } ${ count > 1 ? ` (x${ count } )` : "" } ` ) ;
187+ }
188+ }
189+ }
190+ }
191+
192+ function printSummary ( filesScanned , violations , useErrorStream = false ) {
193+ const out = useErrorStream ? console . error : console . log ;
194+ const types = [ ...new Set ( violations . map ( ( violation ) => violation . type ) ) ] . sort ( ) ;
195+ const typeSummary = types . length > 0 ? types . join ( ", " ) : "none" ;
196+ out ( `Summary: files_scanned=${ filesScanned } ` ) ;
197+ out ( `Summary: total_violations=${ violations . length } ` ) ;
198+ out ( `Summary: violation_types=${ typeSummary } ` ) ;
199+ }
200+
92201async function run ( ) {
93202 const repoRoot = process . cwd ( ) ;
94203 const filesToScan = [ ] ;
@@ -108,15 +217,13 @@ async function run() {
108217
109218 if ( violations . length === 0 ) {
110219 console . log ( "Shared extraction guard passed. No violations found." ) ;
220+ printSummary ( filesToScan . length , violations ) ;
111221 process . exit ( 0 ) ;
112222 }
113223
114224 console . error ( `Shared extraction guard failed with ${ violations . length } violation(s).` ) ;
115- for ( const violation of violations ) {
116- console . error (
117- `${ violation . file } | ${ violation . type } | ${ violation . match } `
118- ) ;
119- }
225+ printGroupedViolations ( violations ) ;
226+ printSummary ( filesToScan . length , violations , true ) ;
120227 process . exit ( 1 ) ;
121228}
122229
0 commit comments