import chalk from 'chalk' ; import fs from 'fs' ; import os from 'os' ; import strip from 'strip-ansi' ; import { ErrorPayload } from 'vite' ; import { URI } from 'vscode-uri' ; import { parentPort , isMainThread } from 'worker_threads' ; import { codeFrameColumns , SourceLocation } from '@babel/code-frame' ; import { ACTION_TYPES } from './types' ; import type { Range } from 'vscode-languageclient' ; import type { ESLint } from 'eslint' ; import type { Diagnostic as LspDiagnostic , PublishDiagnosticsParams , } from 'vscode-languageclient/node' ; import type { Diagnostic as TsDiagnostic , flattenDiagnosticMessageText as flattenDiagnosticMessageTextType , LineAndCharacter , } from 'typescript' ; export interface NormalizedDiagnostic { /** error message */ message ? : string ; /** error conclusion */ conclusion ? : string ; /** error stack */ stack ? : string | string [ ] ; /** file name */ id ? : string ; /** checker diagnostic source */ checker : string ; /** raw code frame generated by @babel/code-frame */ codeFrame ? : string ; /** code frame, but striped */ stripedCodeFrame ? : string ; /** error code location */ loc ? : SourceLocation ; /** error level */ level ? : DiagnosticLevel ; } export enum DiagnosticLevel { Warning = 0 , Error = 1 , Suggestion = 2 , Message = 3 } export function diagnosticToTerminalLog ( d , name ? ) { const nameInLabel = name ? `(${ name })` : '' ; const boldBlack = chalk . bold . rgb ( 0 , 0 , 0 ) ; const labelMap = { [ DiagnosticLevel . Error ] : boldBlack . bgRedBright ( ` ERROR${ nameInLabel } ` ) , [ DiagnosticLevel . Warning ] : boldBlack . bgYellowBright ( ` WARNING${ nameInLabel } ` ) , [ DiagnosticLevel . Suggestion ] : boldBlack . bgBlueBright ( ` SUGGESTION${ nameInLabel } ` ) , [ DiagnosticLevel . Message ] : boldBlack . bgCyanBright ( ` MESSAGE${ nameInLabel } ` ) , } ; const levelLabel = labelMap [ d . level ?? DiagnosticLevel . Error ] ; const fileLabel = boldBlack . bgCyanBright ( ' FILE ' ) + ' ' ; const position = d . loc ? chalk . yellow ( d . loc . start . line ) + ':' + chalk . yellow ( d . loc . start . column ) : '' ; return [ levelLabel + ' ' + d . message , fileLabel + d . id + ':' + position + os . EOL , d . codeFrame + os . EOL , d . conclusion , ] . filter ( Boolean ) . join ( os . EOL ) ; } export function diagnosticToViteError ( diagnostics ) { const d = Array . isArray ( diagnostics ) ? diagnostics [ 0 ] : diagnostics ; let loc ; if ( d . loc ) { loc = { file : d . id , line : d . loc . start . line , column : typeof d . loc . start . column === 'number' ? d . loc . start . column : 0 , } ; } return { message : d . message ?? '' , stack : typeof d . stack === 'string' ? d . stack : Array . isArray ( d . stack ) ? d . stack . join ( os . EOL ) : '' , id : d . id , frame : d . stripedCodeFrame , plugin : `vite-plugin-checker(${ d . checker })` , loc , } ; } export function createFrame ( { source , location , } ) { const frame = codeFrameColumns ( source , location , { forceColor : true , } ) . split ( '\n' ) . map ( ( line ) => '  ' + line ) . join ( os . EOL ) ; return frame ; } export function tsLocationToBabelLocation ( tsLoc ) { return { start : { line : tsLoc . start . line + 1 , column : tsLoc . start . character + 1 } , end : { line : tsLoc . end . line + 1 , column : tsLoc . end . character + 1 } , } ; } export function normalizeTsDiagnostic ( d ) { const fileName = d . file ?. fileName ; const { flattenDiagnosticMessageText , } = require ( 'typescript' ) ; const message = flattenDiagnosticMessageText ( d . messageText , os . EOL ) ; let loc ; const pos = d . start === undefined ? null : d . file ?. getLineAndCharacterOfPosition ( d . start ) ; if ( pos && d . file && typeof d . start === 'number' && typeof d . length === 'number' ) { loc = tsLocationToBabelLocation ( { start : d . file ?. getLineAndCharacterOfPosition ( d . start ) , end : d . file ?. getLineAndCharacterOfPosition ( d . start + d . length ) , } ) ; } let codeFrame ; if ( loc ) { codeFrame = createFrame ( { source : d . file ! . text , location : loc , } ) ; } return { message , conclusion : '' , codeFrame , stripedCodeFrame : codeFrame && strip ( codeFrame ) , id : fileName , checker : 'TypeScript' , loc , level : d . category as any as DiagnosticLevel , } ; } export function normalizeLspDiagnostic ( { diagnostic , absFilePath , fileText , } ) { let level = DiagnosticLevel . Error ; const loc = lspRange2Location ( diagnostic . range ) ; const codeFrame = codeFrameColumns ( fileText , loc ) ; switch ( diagnostic . severity ) { case 1 : level = DiagnosticLevel . Error ; break ; case 2 : level = DiagnosticLevel . Warning ; break ; case 3 : level = DiagnosticLevel . Message ; break ; case 4 : level = DiagnosticLevel . Suggestion ; break ; } return { message : diagnostic . message . trim ( ) , conclusion : '' , codeFrame , stripedCodeFrame : codeFrame && strip ( codeFrame ) , id : absFilePath , checker : 'VLS' , loc , level , } ; } export async function normalizePublishDiagnosticParams ( publishDiagnostics ) { const diagnostics = publishDiagnostics . diagnostics ; const absFilePath = uriToAbsPath ( publishDiagnostics . uri ) ; const { readFile } = fs . promises ; const fileText = await readFile ( absFilePath , 'utf-8' ) ; const res = diagnostics . map ( ( d ) => { return normalizeLspDiagnostic ( { diagnostic : d , absFilePath , fileText , } ) ; } ) ; return res ; } export function uriToAbsPath ( documentUri ) { return URI . parse ( documentUri ) . fsPath ; } export function lspRange2Location ( range ) { return { start : { line : range . start . line + 1 , column : range . start . character + 1 , } , end : { line : range . end . line + 1 , column : range . end . character + 1 , } , } ; } const isNormalizedDiagnostic = ( d ) : d is NormalizedDiagnostic => { return Boolean ( d ) ; } ; export function normalizeEslintDiagnostic ( diagnostic ) { return diagnostic . messages . map ( ( d ) => { let level = DiagnosticLevel . Error ; switch ( d . severity ) { case 0 : level = DiagnosticLevel . Error ; return null ; case 1 : level = DiagnosticLevel . Warning ; break ; case 2 : level = DiagnosticLevel . Error ; break ; } const loc = { start : { line : d . line , column : d . column , } , end : { line : d . endLine || 0 , column : d . endColumn , } , } ; const codeFrame = createFrame ( { source : diagnostic . source ?? '' , location : loc , } ) ; return { message : d . message , conclusion : '' , codeFrame , stripedCodeFrame : codeFrame && strip ( codeFrame ) , id : diagnostic . filePath , checker : 'ESLint' , loc , level , } as any as NormalizedDiagnostic ; } ) . filter ( isNormalizedDiagnostic ) ; } export function ensureCall ( callback ) { setTimeout ( ( ) => { callback ( ) ; } ) ; } export function consoleLog ( value ) { if ( isMainThread ) { console . log ( value ) ; } else { parentPort ?. postMessage ( { type : ACTION_TYPES . console , payload : value , } ) ; } }