@@ -928,6 +928,95 @@ function collapsePlaceholderPythonDataclasses(code: string, knownDefinitionNames
928928 return code . replace ( / \n { 3 , } / g, "\n\n" ) ;
929929}
930930
931+ function removeUnusedSyntheticPythonDataclasses ( code : string , knownDefinitionNames : Set < string > ) : string {
932+ interface DataclassBlock {
933+ name : string ;
934+ text : string ;
935+ start : number ;
936+ end : number ;
937+ synthetic : boolean ;
938+ }
939+
940+ const classBlockRe =
941+ / ( (?: ^ # (?: E x p e r i m e n t a l | D e p r e c a t e d | I n t e r n a l ) : [ ^ \n ] * \r ? \n ) * @ d a t a c l a s s (?: \( [ ^ \r \n ] * \) ) ? \r ? \n c l a s s \s + ( \w + ) : [ \s \S ] * ?) (? = ^ (?: # (?: E x p e r i m e n t a l | D e p r e c a t e d | I n t e r n a l ) : [ ^ \n ] * \r ? \n ) * @ d a t a c l a s s (?: \( [ ^ \r \n ] * \) ) ? \r ? \n c l a s s \s + \w | ^ c l a s s \s + \w | ^ d e f \s + \w | ^ [ A - Z ] \w + \s * = | \Z ) / gm;
942+ const blocks : DataclassBlock [ ] = [ ...code . matchAll ( classBlockRe ) ] . map ( ( match ) => ( {
943+ name : match [ 2 ] ,
944+ text : match [ 1 ] ,
945+ start : match . index ?? 0 ,
946+ end : ( match . index ?? 0 ) + match [ 1 ] . length ,
947+ synthetic : ! knownDefinitionNames . has ( match [ 2 ] . toLowerCase ( ) ) ,
948+ } ) ) ;
949+ const syntheticBlocks = blocks . filter ( ( block ) => block . synthetic ) ;
950+ if ( syntheticBlocks . length === 0 ) return code ;
951+
952+ let outsideSyntheticBlocks = "" ;
953+ let cursor = 0 ;
954+ for ( const block of syntheticBlocks ) {
955+ outsideSyntheticBlocks += code . slice ( cursor , block . start ) ;
956+ cursor = block . end ;
957+ }
958+ outsideSyntheticBlocks += code . slice ( cursor ) ;
959+
960+ const syntheticNames = new Set ( syntheticBlocks . map ( ( block ) => block . name ) ) ;
961+ const dependencies = new Map < string , Set < string > > ( ) ;
962+ const live = new Set < string > ( ) ;
963+
964+ for ( const block of syntheticBlocks ) {
965+ const referenceRe = new RegExp ( `\\b${ escapeRegExp ( block . name ) } \\b` ) ;
966+ if ( referenceRe . test ( outsideSyntheticBlocks ) ) {
967+ live . add ( block . name ) ;
968+ }
969+
970+ const blockDependencies = new Set < string > ( ) ;
971+ for ( const dependency of syntheticNames ) {
972+ if ( dependency === block . name ) continue ;
973+ const dependencyRe = new RegExp ( `\\b${ escapeRegExp ( dependency ) } \\b` ) ;
974+ if ( dependencyRe . test ( block . text ) ) {
975+ blockDependencies . add ( dependency ) ;
976+ }
977+ }
978+ dependencies . set ( block . name , blockDependencies ) ;
979+ }
980+
981+ const worklist = [ ...live ] ;
982+ while ( worklist . length > 0 ) {
983+ const name = worklist . pop ( ) ! ;
984+ for ( const dependency of dependencies . get ( name ) ?? [ ] ) {
985+ if ( live . has ( dependency ) ) continue ;
986+ live . add ( dependency ) ;
987+ worklist . push ( dependency ) ;
988+ }
989+ }
990+
991+ const blocksToRemove = new Set ( syntheticBlocks . filter ( ( block ) => ! live . has ( block . name ) ) . map ( ( block ) => block . name ) ) ;
992+ if ( blocksToRemove . size === 0 ) return code ;
993+
994+ const appendSegment = ( parts : string [ ] , segment : string ) : void => {
995+ if ( parts . length === 0 || segment . length === 0 ) {
996+ parts . push ( segment ) ;
997+ return ;
998+ }
999+ const previous = parts [ parts . length - 1 ] ;
1000+ const trailingNewlines = previous . match ( / \n + $ / ) ?. [ 0 ] . length ?? 0 ;
1001+ const leadingNewlines = segment . match ( / ^ \n + / ) ?. [ 0 ] . length ?? 0 ;
1002+ if ( trailingNewlines + leadingNewlines > 2 ) {
1003+ segment = "\n" . repeat ( Math . max ( 0 , 2 - trailingNewlines ) ) + segment . slice ( leadingNewlines ) ;
1004+ }
1005+ parts . push ( segment ) ;
1006+ } ;
1007+
1008+ const parts : string [ ] = [ ] ;
1009+ cursor = 0 ;
1010+ for ( const block of blocks ) {
1011+ if ( ! blocksToRemove . has ( block . name ) ) continue ;
1012+ appendSegment ( parts , code . slice ( cursor , block . start ) ) ;
1013+ cursor = block . end ;
1014+ }
1015+ appendSegment ( parts , code . slice ( cursor ) ) ;
1016+
1017+ return parts . join ( "" ) ;
1018+ }
1019+
9311020/**
9321021 * Reorder Python class/enum definitions so forward references are resolved.
9331022 * Quicktype may emit classes in an order where a class references another
@@ -3261,6 +3350,10 @@ def _patch_model_capabilities(data: dict) -> dict:
32613350 finalCode = applyUnionRewritesToPython ( finalCode , refBasedUnions ) ;
32623351 finalCode = postProcessDiscriminatorDefaultsForPython ( finalCode , refBasedUnions ) ;
32633352 finalCode = unwrapRedundantPythonLambdas ( finalCode ) ;
3353+ finalCode = removeUnusedSyntheticPythonDataclasses (
3354+ finalCode ,
3355+ new Set ( Object . keys ( allDefinitions ) . map ( ( name ) => name . toLowerCase ( ) ) )
3356+ ) ;
32643357
32653358 // Apply `_`-prefix to type names of internal RPC types so the leading-underscore
32663359 // Python convention signals "internal, no stability guarantees" to consumers.
0 commit comments