@@ -26,7 +26,7 @@ export const runTsProgram = async (
2626 compilerCtx : d . CompilerCtx ,
2727 buildCtx : d . BuildCtx ,
2828 tsBuilder : ts . BuilderProgram ,
29- ) : Promise < boolean > => {
29+ ) : Promise < string [ ] > => {
3030 const tsSyntactic = loadTypeScriptDiagnostics ( tsBuilder . getSyntacticDiagnostics ( ) ) ;
3131 const tsGlobal = loadTypeScriptDiagnostics ( tsBuilder . getGlobalDiagnostics ( ) ) ;
3232 const tsOptions = loadTypeScriptDiagnostics ( tsBuilder . getOptionsDiagnostics ( ) ) ;
@@ -35,7 +35,7 @@ export const runTsProgram = async (
3535 buildCtx . diagnostics . push ( ...tsOptions ) ;
3636
3737 if ( buildCtx . hasError ) {
38- return false ;
38+ return [ ] ;
3939 }
4040
4141 const tsProgram = tsBuilder . getProgram ( ) ;
@@ -136,7 +136,54 @@ export const runTsProgram = async (
136136 validateTranspiledComponents ( config , buildCtx ) ;
137137
138138 if ( buildCtx . hasError ) {
139- return false ;
139+ return [ ] ;
140+ }
141+
142+ return emittedDts ;
143+ } ;
144+
145+ /**
146+ * Generate types and run semantic validation AFTER components.d.ts exists on disk
147+ */
148+ export const validateTypesAfterGeneration = async (
149+ config : d . ValidatedConfig ,
150+ compilerCtx : d . CompilerCtx ,
151+ buildCtx : d . BuildCtx ,
152+ tsBuilder : ts . BuilderProgram ,
153+ emittedDts : string [ ] ,
154+ ) : Promise < boolean > => {
155+ const tsProgram = tsBuilder . getProgram ( ) ;
156+ const typesOutputTarget = config . outputTargets . filter ( isOutputTargetDistTypes ) ;
157+
158+ // Check if components.d.ts already exists
159+ const componentsDtsPath = join ( config . srcDir , 'components.d.ts' ) ;
160+ const componentsDtsExists = await compilerCtx . fs . access ( componentsDtsPath ) ;
161+
162+ // Only validate source files if components.d.ts already exists
163+ // If it doesn't exist yet (first build), skip validation to avoid chicken-and-egg errors
164+ if ( config . validateTypes && componentsDtsExists ) {
165+ const sourceFiles = tsProgram . getSourceFiles ( ) . filter ( ( sf ) => {
166+ const fileName = normalizePath ( sf . fileName ) ;
167+ return (
168+ ! fileName . includes ( 'node_modules' ) &&
169+ ! fileName . endsWith ( '.d.ts' ) &&
170+ fileName . startsWith ( normalizePath ( config . srcDir ) )
171+ ) ;
172+ } ) ;
173+
174+ for ( const sourceFile of sourceFiles ) {
175+ const sourceSemanticDiagnostics = tsProgram . getSemanticDiagnostics ( sourceFile ) ;
176+ const tsSemantic = loadTypeScriptDiagnostics ( sourceSemanticDiagnostics ) ;
177+
178+ if ( config . devMode ) {
179+ tsSemantic . forEach ( ( semanticDiagnostic ) => {
180+ if ( semanticDiagnostic . code === '6133' || semanticDiagnostic . code === '6192' ) {
181+ semanticDiagnostic . level = 'warn' ;
182+ }
183+ } ) ;
184+ }
185+ buildCtx . diagnostics . push ( ...tsSemantic ) ;
186+ }
140187 }
141188
142189 // create the components.d.ts file and write to disk
@@ -164,21 +211,8 @@ export const runTsProgram = async (
164211 await Promise . all ( srcRootDtsFiles ) ;
165212 }
166213
167- // TODO(STENCIL-540): remove `hasTypesChanged` check and figure out how to generate types before
168- // executing the TS build program so we don't get semantic diagnostic errors about referencing the
169- // auto-generated `components.d.ts` file.
170- if ( config . validateTypes && ! hasTypesChanged ) {
171- const tsSemantic = loadTypeScriptDiagnostics ( tsBuilder . getSemanticDiagnostics ( ) ) ;
172- if ( config . devMode ) {
173- tsSemantic . forEach ( ( semanticDiagnostic ) => {
174- // Unused variable errors become warnings in dev mode
175- if ( semanticDiagnostic . code === '6133' || semanticDiagnostic . code === '6192' ) {
176- semanticDiagnostic . level = 'warn' ;
177- }
178- } ) ;
179- }
180- buildCtx . diagnostics . push ( ...tsSemantic ) ;
181- }
214+ // Note: We validated user source files above before generating types
215+ // We don't validate components.d.ts itself as it may reference types that will resolve later
182216
183217 return hasTypesChanged ;
184218} ;
0 commit comments