import _ts from 'typescript' ; import { isTSVueFile , toRawVueFileName , isRawVueFile , toTSVueFIleName } from './path' ; import { pos2location , createSourceFile , location2pos , getFullTextFromSnapshot } from './node' ; import { SourcemapEntry , DiagnosticWithRange , FileEntry } from '../types' ; import { readSystemFile } from './file' ; import { findNode } from '../ast' ; import { getOriginalPositionFor } from '../sourcemap' ; import { Logger } from '../logger' ; const REQUIRE_PROPS_CODE = 2554 ; const EXTERNAL_COMPONENT_IDENTIFIER_REGEXP = /^_component_.*$/ ; export const hasDiagRange = ( diagnostic ) : diagnostic is DiagnosticWithRange => { const { start , length , file } = diagnostic ; return typeof start === 'number' && typeof length === 'number' && ! ! file ; } ; export const translateTSVuefileDiagnostic = ( diagnostic , sourcemapEntry , typeChecker , ts , logger ? , fileEntry ? ) : _ts . Diagnostic => { const { file } = diagnostic ; if ( ! file || ( ! isTSVueFile ( file . fileName ) && ! isRawVueFile ( file . fileName ) ) ) { throw new Error ( 'Invalid diagnostic format' ) ; } if ( ! hasDiagRange ( diagnostic ) ) { return diagnostic ; } const tsVueFileName = isTSVueFile ( file . fileName ) ? file . fileName : toTSVueFIleName ( file . fileName ) ; const rawVueFileName = isRawVueFile ( file . fileName ) ? file . fileName : toRawVueFileName ( file . fileName ) ; let originalContent ; if ( fileEntry ) { const file = fileEntry . get ( rawVueFileName ) ; if ( file ) { originalContent = getFullTextFromSnapshot ( file . scriptSnapshot ) ; } else { originalContent = readSystemFile ( rawVueFileName ) ; } } else { originalContent = readSystemFile ( rawVueFileName ) ; } if ( ! originalContent ) { throw new Error ( `could not find file: ${ rawVueFileName }` ) ; } const sourcefile = createSourceFile ( ts , originalContent , rawVueFileName ) ; const { start , length } = diagnostic ; const content = file . getFullText ( ) ; const startLC = pos2location ( content , start ) ; const endLC = pos2location ( content , start + length ) ; const originalStartLC = getOriginalPositionFor ( tsVueFileName , startLC , sourcemapEntry ) ; const originalEndLC = getOriginalPositionFor ( tsVueFileName , endLC , sourcemapEntry ) ; const originalStartPos = location2pos ( originalContent , originalStartLC ) ; const originalEndPos = location2pos ( originalContent , originalEndLC ) ; const translatedMessage = translateDiagnosticMessage ( diagnostic , typeChecker , ts , logger ) ; return { ... diagnostic , file : sourcefile , start : originalStartPos , length : originalEndPos - originalStartPos , messageText : translatedMessage } ; } ; export const translateDiagnosticMessage = ( diagnostic , typeChecker , ts , logger ? ) : string | _ts . DiagnosticMessageChain => { const { code , start , messageText , file } = diagnostic ; if ( logger ) { logger . info ( code ) ; logger . info ( messageText ) ; } const translationTargetCodes = [ REQUIRE_PROPS_CODE ] ; let translatedMessage = messageText ; if ( ! translationTargetCodes . includes ( code ) ) return translatedMessage ; if ( diagnostic . code === REQUIRE_PROPS_CODE ) { const node = findNode ( file , start ) ; if ( ! node ) return translatedMessage ; const _createVNode = node . parent ; if ( ! ts . isCallExpression ( _createVNode ) ) return translatedMessage ; const firstArg = _createVNode . arguments [ 0 ] ; if ( ! firstArg ) return translatedMessage ; /**
         * Check if it's an external component node
         */ const typeInfo = typeChecker . getTypeAtLocation ( firstArg ) ; const isLiteralType = typeInfo . flags === ts . TypeFlags . StringLiteral ; if ( ! isLiteralType ) return translatedMessage ; const isExternalComponentNode = ts . isIdentifier ( firstArg ) && EXTERNAL_COMPONENT_IDENTIFIER_REGEXP . test ( firstArg . escapedText . toString ( ) ) ; if ( ! isExternalComponentNode ) return translatedMessage ; const tagName = ( typeInfo as _ts . LiteralType ) . value . toString ( ) ; translatedMessage = `Missing required props for <${ tagName }/> component.` ; const requiredPropsNames = findRequiredPropNames ( file , tagName , typeChecker , ts , logger ) ; if ( requiredPropsNames != null && requiredPropsNames . length >= 1 ) { translatedMessage = `Missing props [${ requiredPropsNames . join ( ',' ) }] for <${ tagName }/> component.` ; } return translatedMessage ; } return translatedMessage ; } ; export function findRequiredPropNames ( file , componentName , typeChecker , ts , logger ? ) { const findPropsDict = ( node ) : _ts . Node | undefined => { if ( ts . isTypeAliasDeclaration ( node ) && node . name . escapedText . toString ( ) === '___ExternalComponentsProps' ) { return node ; } return ts . forEachChild ( node , findPropsDict ) ; } ; const propsDict = findPropsDict ( file ) ! ; const findTargetProperty = ( node ) : _ts . Node | undefined => { if ( ts . isPropertySignature ( node ) && ( node . name as _ts . Identifier ) . escapedText . toString ( ) === componentName ) { return node ; } return ts . forEachChild ( node , findTargetProperty ) ; } ; const targetComponent = findTargetProperty ( propsDict ) ! ; const propType = typeChecker . getTypeAtLocation ( targetComponent ) ; const typeString = typeChecker . typeToString ( propType ) ; if ( typeString === 'never' ) return null ; const requiredPropNames = typeString . split ( '|' ) . map ( s => s . trim ( ) ) ; return requiredPropNames ; }