@@ -26,6 +26,7 @@ import {
26
26
CodeCellUpdateAttrsType ,
27
27
CellErrorPayloadType ,
28
28
AiGeneratedCellPayloadType ,
29
+ TsServerDiagnosticType ,
29
30
} from '@srcbook/shared' ;
30
31
import { useSettings } from '@/components/use-settings' ;
31
32
import { cn } from '@/lib/utils' ;
@@ -41,6 +42,7 @@ import { useDebouncedCallback } from 'use-debounce';
41
42
import { EditorView } from 'codemirror' ;
42
43
import { EditorState } from '@codemirror/state' ;
43
44
import { unifiedMergeView } from '@codemirror/merge' ;
45
+ import { type Diagnostic , linter } from '@codemirror/lint' ;
44
46
45
47
const DEBOUNCE_DELAY = 500 ;
46
48
type CellModeType = 'off' | 'generating' | 'reviewing' | 'prompting' | 'fixing' ;
@@ -543,6 +545,84 @@ function Header(props: {
543
545
) ;
544
546
}
545
547
548
+ function tsCategoryToSeverity (
549
+ diagnostic : Pick < TsServerDiagnosticType , 'category' | 'code' > ,
550
+ ) : Diagnostic [ 'severity' ] {
551
+ if ( diagnostic . code === 7027 ) {
552
+ return 'warning' ;
553
+ }
554
+ // force resolve types with fallback
555
+ switch ( diagnostic . category ) {
556
+ case 'error' :
557
+ return 'error' ;
558
+ case 'warning' :
559
+ return 'warning' ;
560
+ case 'suggestion' :
561
+ return 'warning' ;
562
+ case 'info' :
563
+ return 'info' ;
564
+ default :
565
+ return 'info' ;
566
+ }
567
+ }
568
+
569
+ function isDiagnosticWithLocation (
570
+ diagnostic : TsServerDiagnosticType ,
571
+ ) : diagnostic is TsServerDiagnosticType {
572
+ return ! ! ( typeof diagnostic . start . line === 'number' && typeof diagnostic . end . line === 'number' ) ;
573
+ }
574
+
575
+ function tsDiagnosticMessage ( diagnostic : TsServerDiagnosticType ) : string {
576
+ if ( typeof diagnostic . text === 'string' ) {
577
+ return diagnostic . text ;
578
+ }
579
+ return JSON . stringify ( diagnostic ) ; // Fallback
580
+ }
581
+
582
+ function convertTSDiagnosticToCM ( diagnostic : TsServerDiagnosticType , code : string ) : Diagnostic {
583
+ const message = tsDiagnosticMessage ( diagnostic ) ;
584
+
585
+ // parse conversion TS server is {line, offset} to CodeMirror {from, to} in absolute chars
586
+ return {
587
+ from : Math . min (
588
+ code . length - 1 ,
589
+ code
590
+ . split ( '\n' )
591
+ . slice ( 0 , diagnostic . start . line - 1 )
592
+ . join ( '\n' ) . length + diagnostic . start . offset ,
593
+ ) ,
594
+ to : Math . min (
595
+ code . length - 1 ,
596
+ code
597
+ . split ( '\n' )
598
+ . slice ( 0 , diagnostic . end . line - 1 )
599
+ . join ( '\n' ) . length + diagnostic . end . offset ,
600
+ ) ,
601
+ message : message ,
602
+ severity : tsCategoryToSeverity ( diagnostic ) ,
603
+ } ;
604
+ }
605
+
606
+ function tsLinter (
607
+ cell : CodeCellType ,
608
+ getTsServerDiagnostics : ( id : string ) => TsServerDiagnosticType [ ] ,
609
+ getTsServerSuggestions : ( id : string ) => TsServerDiagnosticType [ ] ,
610
+ ) {
611
+ const semanticDiagnostics = getTsServerDiagnostics ( cell . id ) ;
612
+ const syntaticDiagnostics = getTsServerSuggestions ( cell . id ) ;
613
+ const diagnostics = [ ...syntaticDiagnostics , ...semanticDiagnostics ] . filter (
614
+ isDiagnosticWithLocation ,
615
+ ) ;
616
+
617
+ const cm_diagnostics = diagnostics . map ( ( diagnostic ) => {
618
+ return convertTSDiagnosticToCM ( diagnostic , cell . source ) ;
619
+ } ) ;
620
+
621
+ return linter ( async ( ) : Promise < readonly Diagnostic [ ] > => {
622
+ return cm_diagnostics ;
623
+ } ) ;
624
+ }
625
+
546
626
function CodeEditor ( {
547
627
cell,
548
628
runCell,
@@ -555,7 +635,11 @@ function CodeEditor({
555
635
readOnly : boolean ;
556
636
} ) {
557
637
const { codeTheme } = useTheme ( ) ;
558
- const { updateCell : updateCellOnClient } = useCells ( ) ;
638
+ const {
639
+ updateCell : updateCellOnClient ,
640
+ getTsServerDiagnostics,
641
+ getTsServerSuggestions,
642
+ } = useCells ( ) ;
559
643
560
644
const updateCellOnServerDebounced = useDebouncedCallback ( updateCellOnServer , DEBOUNCE_DELAY ) ;
561
645
@@ -566,6 +650,8 @@ function CodeEditor({
566
650
567
651
let extensions = [
568
652
javascript ( { typescript : true } ) ,
653
+ // wordHoverExtension,
654
+ tsLinter ( cell , getTsServerDiagnostics , getTsServerSuggestions ) ,
569
655
Prec . highest ( keymap . of ( [ { key : 'Mod-Enter' , run : evaluateModEnter } ] ) ) ,
570
656
] ;
571
657
if ( readOnly ) {
0 commit comments