@@ -234,6 +234,7 @@ export const createTypeDiagnosticsController = ({
234234 getTypeScriptLibUrls,
235235 getTypePackageFileUrls,
236236 getJsxSource,
237+ getWorkspaceTabs = ( ) => [ ] ,
237238 getRenderMode = ( ) => 'dom' ,
238239 defaultTypeScriptLibFileName = 'lib.esnext.full.d.ts' ,
239240 setTypecheckButtonLoading,
@@ -248,6 +249,116 @@ export const createTypeDiagnosticsController = ({
248249 getActiveTypeDiagnosticsRuns,
249250 onIssuesDetected = ( ) => { } ,
250251} ) => {
252+ const styleTabLanguages = new Set ( [ 'css' , 'less' , 'sass' , 'module' ] )
253+
254+ const isStyleWorkspaceTab = tab => {
255+ if ( ! tab || typeof tab !== 'object' ) {
256+ return false
257+ }
258+
259+ const language =
260+ typeof tab . language === 'string' ? tab . language . trim ( ) . toLowerCase ( ) : ''
261+ if ( styleTabLanguages . has ( language ) ) {
262+ return true
263+ }
264+
265+ const path = typeof tab . path === 'string' ? tab . path . trim ( ) . toLowerCase ( ) : ''
266+ return / \. ( c s s | l e s s | s a s s | s c s s ) $ / . test ( path )
267+ }
268+
269+ const toWorkspaceComponentTabs = ( ) => {
270+ const tabs = typeof getWorkspaceTabs === 'function' ? getWorkspaceTabs ( ) : [ ]
271+ if ( ! Array . isArray ( tabs ) ) {
272+ return [ ]
273+ }
274+
275+ return tabs
276+ . filter ( tab => tab && typeof tab === 'object' && ! isStyleWorkspaceTab ( tab ) )
277+ . map ( tab => ( {
278+ ...tab ,
279+ path :
280+ typeof tab . path === 'string' && tab . path . trim ( ) . length > 0
281+ ? normalizeRelativePath ( tab . path )
282+ : typeof tab . name === 'string' && tab . name . trim ( ) . length > 0
283+ ? normalizeRelativePath ( tab . name )
284+ : '' ,
285+ content : typeof tab . content === 'string' ? tab . content : '' ,
286+ } ) )
287+ . filter ( tab => tab . path . length > 0 )
288+ }
289+
290+ const resolveWorkspaceEntryForTypecheck = tabs => {
291+ if ( ! Array . isArray ( tabs ) || tabs . length === 0 ) {
292+ return null
293+ }
294+
295+ return (
296+ tabs . find ( tab => tab ?. role === 'entry' && typeof tab . path === 'string' ) ??
297+ tabs . find ( tab => tab ?. id === 'component' && typeof tab . path === 'string' ) ??
298+ tabs [ 0 ] ??
299+ null
300+ )
301+ }
302+
303+ const toJsCompatibilityCandidates = ( specifier , containingFile ) => {
304+ if ( typeof specifier !== 'string' || ! specifier . startsWith ( '.' ) ) {
305+ return [ ]
306+ }
307+
308+ const containingDirectory = dirname ( containingFile )
309+ const resolvedSpecifier = joinPath ( containingDirectory , specifier )
310+ const withoutJsExtension = resolvedSpecifier . replace ( / \. ( m j s | c j s | j s x | j s ) $ / i, '' )
311+
312+ if ( withoutJsExtension === resolvedSpecifier ) {
313+ return [ ]
314+ }
315+
316+ const candidates = [
317+ `${ withoutJsExtension } .ts` ,
318+ `${ withoutJsExtension } .tsx` ,
319+ `${ withoutJsExtension } .mts` ,
320+ `${ withoutJsExtension } .cts` ,
321+ `${ withoutJsExtension } /index.ts` ,
322+ `${ withoutJsExtension } /index.tsx` ,
323+ `${ withoutJsExtension } /index.mts` ,
324+ `${ withoutJsExtension } /index.cts` ,
325+ ]
326+
327+ return [ ...new Set ( candidates ) ]
328+ }
329+
330+ const toTypeScriptExtension = ( compiler , fileName ) => {
331+ if ( fileName . endsWith ( '.tsx' ) ) {
332+ return compiler . Extension ?. Tsx ?? '.tsx'
333+ }
334+
335+ if ( fileName . endsWith ( '.ts' ) ) {
336+ return compiler . Extension ?. Ts ?? '.ts'
337+ }
338+
339+ if ( fileName . endsWith ( '.mts' ) ) {
340+ return compiler . Extension ?. Mts ?? '.mts'
341+ }
342+
343+ if ( fileName . endsWith ( '.cts' ) ) {
344+ return compiler . Extension ?. Cts ?? '.cts'
345+ }
346+
347+ if ( fileName . endsWith ( '.d.ts' ) ) {
348+ return compiler . Extension ?. Dts ?? '.d.ts'
349+ }
350+
351+ if ( fileName . endsWith ( '.jsx' ) ) {
352+ return compiler . Extension ?. Jsx ?? '.jsx'
353+ }
354+
355+ if ( fileName . endsWith ( '.js' ) ) {
356+ return compiler . Extension ?. Js ?? '.js'
357+ }
358+
359+ return compiler . Extension ?. Ts ?? '.ts'
360+ }
361+
251362 let typeCheckRunId = 0
252363 let typeScriptCompiler = null
253364 let typeScriptCompilerProvider = null
@@ -690,7 +801,10 @@ export const createTypeDiagnosticsController = ({
690801 }
691802
692803 const collectTypeDiagnostics = async ( compiler , sourceText ) => {
693- const sourceFileName = 'component.tsx'
804+ const workspaceComponentTabs = toWorkspaceComponentTabs ( )
805+ const resolvedEntryTab = resolveWorkspaceEntryForTypecheck ( workspaceComponentTabs )
806+ const sourceFileName = resolvedEntryTab ?. path || 'component.tsx'
807+ const typecheckSourceFileName = sourceFileName . replace ( / \. ( j s x ? | m j s | c j s ) $ / i, '.tsx' )
694808 const jsxTypesFileName = 'knighted-jsx-runtime.d.ts'
695809 const renderMode = getRenderMode ( )
696810 const isReactMode = renderMode === 'react'
@@ -701,7 +815,15 @@ export const createTypeDiagnosticsController = ({
701815 reactTypes = await ensureReactTypeFiles ( compiler )
702816 }
703817
704- const files = new Map ( [ [ sourceFileName , sourceText ] , ...libFiles . entries ( ) ] )
818+ const files = new Map ( libFiles . entries ( ) )
819+
820+ if ( workspaceComponentTabs . length > 0 ) {
821+ for ( const tab of workspaceComponentTabs ) {
822+ files . set ( tab . path , tab . content )
823+ }
824+ }
825+
826+ files . set ( typecheckSourceFileName , sourceText )
705827
706828 if ( ! isReactMode ) {
707829 files . set ( jsxTypesFileName , domJsxTypes )
@@ -723,6 +845,8 @@ export const createTypeDiagnosticsController = ({
723845 compiler . ModuleResolutionKind ?. NodeJs ,
724846 types : [ ] ,
725847 strict : true ,
848+ allowJs : true ,
849+ checkJs : false ,
726850 noEmit : true ,
727851 skipLibCheck : true ,
728852 }
@@ -776,6 +900,22 @@ export const createTypeDiagnosticsController = ({
776900 return resolved . resolvedModule
777901 }
778902
903+ const jsCompatibilityCandidates = toJsCompatibilityCandidates (
904+ moduleName ,
905+ containingFile ,
906+ )
907+ const matchedWorkspaceModule = jsCompatibilityCandidates . find ( candidate =>
908+ files . has ( candidate ) ,
909+ )
910+
911+ if ( matchedWorkspaceModule ) {
912+ return {
913+ resolvedFileName : matchedWorkspaceModule ,
914+ extension : toTypeScriptExtension ( compiler , matchedWorkspaceModule ) ,
915+ isExternalLibraryImport : false ,
916+ }
917+ }
918+
779919 if ( ! reactTypes ) {
780920 return undefined
781921 }
@@ -813,9 +953,13 @@ export const createTypeDiagnosticsController = ({
813953
814954 const scriptKind = normalizedFileName . endsWith ( '.tsx' )
815955 ? compiler . ScriptKind ?. TSX
816- : normalizedFileName . endsWith ( '.d.ts' )
817- ? compiler . ScriptKind ?. TS
818- : compiler . ScriptKind ?. TS
956+ : normalizedFileName . endsWith ( '.jsx' )
957+ ? compiler . ScriptKind ?. JSX
958+ : normalizedFileName . endsWith ( '.js' )
959+ ? compiler . ScriptKind ?. JS
960+ : normalizedFileName . endsWith ( '.d.ts' )
961+ ? compiler . ScriptKind ?. TS
962+ : compiler . ScriptKind ?. TS
819963
820964 return compiler . createSourceFile (
821965 normalizedFileName ,
@@ -834,7 +978,7 @@ export const createTypeDiagnosticsController = ({
834978 resolveModuleNames,
835979 }
836980
837- const rootNames = [ sourceFileName ]
981+ const rootNames = [ typecheckSourceFileName ]
838982 if ( ! isReactMode ) {
839983 rootNames . push ( jsxTypesFileName )
840984 }
0 commit comments