import * as _ from 'lodash' ; import { ts , SyntaxKind } from 'ts-morph' ; import * as _ts from './ts-internal' ; import { JSDocParameterTagExt } from '../app/nodes/jsdoc-parameter-tag.node' ; export class JsdocParserUtil { public isVariableLike ( node ) { if ( node ) { switch ( node . kind ) { case SyntaxKind . BindingElement : case SyntaxKind . EnumMember : case SyntaxKind . Parameter : case SyntaxKind . PropertyAssignment : case SyntaxKind . PropertyDeclaration : case SyntaxKind . PropertySignature : case SyntaxKind . ShorthandPropertyAssignment : case SyntaxKind . VariableDeclaration : return true ; } } return false ; } isTopmostModuleDeclaration ( node ) { if ( node . nextContainer && node . nextContainer . kind === ts . SyntaxKind . ModuleDeclaration ) { let next = < ts . ModuleDeclaration > node . nextContainer ; if ( node . name . end + 1 === next . name . pos ) { return false ; } } return true ; } getRootModuleDeclaration ( node ) { while ( node . parent && node . parent . kind === ts . SyntaxKind . ModuleDeclaration ) { let parent = < ts . ModuleDeclaration > node . parent ; if ( node . name . pos === parent . name . end + 1 ) { node = parent ; } else { break ; } } return node ; } public getMainCommentOfNode ( node , sourceFile ? ) { let description = '' ; if ( node . parent && node . parent . kind === ts . SyntaxKind . VariableDeclarationList ) { node = node . parent . parent ; } else if ( node . kind === ts . SyntaxKind . ModuleDeclaration ) { if ( ! this . isTopmostModuleDeclaration ( < ts . ModuleDeclaration > node ) ) { return null ; } else { node = this . getRootModuleDeclaration ( < ts . ModuleDeclaration > node ) ; } } const comments = _ts . getJSDocCommentRanges ( node , sourceFile . text ) ; if ( comments && comments . length ) { let comment ; if ( node . kind === ts . SyntaxKind . SourceFile ) { if ( comments . length === 1 ) { return null ; } comment = comments [ 0 ] ; } else { comment = comments [ comments . length - 1 ] ; } description = sourceFile . text . substring ( comment . pos , comment . end ) ; } return description ; } public parseComment ( text ) { let comment = '' ; let shortText = 0 ; function readBareLine ( line ) { comment += '\n' + line ; if ( line === '' && shortText === 0 ) { } else if ( line === '' && shortText === 1 ) { shortText = 2 ; } else { if ( shortText === 2 ) { comment += ( comment === '' ? '' : '\n' ) + line ; } } } const CODE_FENCE = /^\s*```(?!.*```)/ ; let inCode = false ; function readLine ( line ) { line = line . replace ( /^\s*\*? ?/ , '' ) ; line = line . replace ( /\s*$/ , '' ) ; if ( CODE_FENCE . test ( line ) ) { inCode = ! inCode ; } if ( ! inCode ) { const tag = /^@(\S+)/ . exec ( line ) ; const SeeTag = /^@see/ . exec ( line ) ; if ( SeeTag ) { line = line . replace ( /^@see/ , 'See' ) ; } if ( tag && ! SeeTag ) { return ; } } readBareLine ( line ) ; } text = text . replace ( /^\s*\/\*+/ , '' ) ; text = text . replace ( /\*+\/\s*$/ , '' ) ; text . split ( /\r\n?|\n/ ) . forEach ( readLine ) ; return comment ; } private getJSDocTags ( node , kind ) { const docs = this . getJSDocs ( node ) ; if ( docs ) { const result = [ ] ; for ( const doc of docs ) { if ( ts . isJSDocParameterTag ( doc ) ) { if ( doc . kind === kind ) { result . push ( doc ) ; } } else if ( ts . isJSDoc ( doc ) ) { result . push ( ... _ . filter ( doc . tags , tag => tag . kind === kind ) ) ; } else { throw new Error ( 'Unexpected type' ) ; } } return result ; } } public getJSDocs ( node ) { let cache = ( node as any ) . jsDocCache ; if ( ! cache ) { cache = this . getJSDocsWorker ( node , [ ] ) . filter ( x => x ) ; ( node as any ) . jsDocCache = cache ; } return cache ; } private getJSDocsWorker ( node , cache ) { const parent = node . parent ; const isInitializerOfVariableDeclarationInStatement = this . isVariableLike ( parent ) && parent . initializer === node && ts . isVariableStatement ( parent . parent . parent ) ; const isVariableOfVariableDeclarationStatement = this . isVariableLike ( node ) && ts . isVariableStatement ( parent . parent ) ; const variableStatementNode = isInitializerOfVariableDeclarationInStatement ? parent . parent . parent : isVariableOfVariableDeclarationStatement ? parent . parent : undefined ; if ( variableStatementNode ) { cache = this . getJSDocsWorker ( variableStatementNode , cache ) ; } const isSourceOfAssignmentExpressionStatement = parent && parent . parent && ts . isBinaryExpression ( parent ) && parent . operatorToken . kind === SyntaxKind . EqualsToken && ts . isExpressionStatement ( parent . parent ) ; if ( isSourceOfAssignmentExpressionStatement ) { cache = this . getJSDocsWorker ( parent . parent , cache ) ; } const isModuleDeclaration = ts . isModuleDeclaration ( node ) && parent && ts . isModuleDeclaration ( parent ) ; const isPropertyAssignmentExpression = parent && ts . isPropertyAssignment ( parent ) ; if ( isModuleDeclaration || isPropertyAssignmentExpression ) { cache = this . getJSDocsWorker ( parent , cache ) ; } if ( ts . isParameter ( node ) ) { cache = _ . concat ( cache , this . getJSDocParameterTags ( node ) ) ; } if ( this . isVariableLike ( node ) && node . initializer ) { cache = _ . concat ( cache , node . initializer . jsDoc ) ; } cache = _ . concat ( cache , node . jsDoc ) ; return cache ; } private getJSDocParameterTags ( param ) { const func = param . parent as ts . FunctionLikeDeclaration ; const tags = this . getJSDocTags ( func , SyntaxKind . JSDocParameterTag ) as ts . JSDocParameterTag [ ] ; if ( ! param . name ) { const i = func . parameters . indexOf ( param ) ; const paramTags = _ . filter ( tags , tag => ts . isJSDocParameterTag ( tag ) ) ; if ( paramTags && 0 <= i && i < paramTags . length ) { return [ paramTags [ i ] ] ; } } else if ( ts . isIdentifier ( param . name ) ) { const name = param . name . text ; return _ . filter ( tags , tag => { if ( ts && ts . isJSDocParameterTag ( tag ) ) { let t = tag ; if ( typeof t . parameterName !== 'undefined' ) { return t . parameterName . text === name ; } else if ( typeof t . name !== 'undefined' ) { if ( typeof t . name . escapedText !== 'undefined' ) { return t . name . escapedText === name ; } } } } ) ; } else { return undefined ; } } public parseJSDocNode ( node ) { let rawDescription = '' ; if ( typeof node . comment === 'string' ) { rawDescription += node . comment ; } else { if ( node . comment ) { const len = node . comment . length ; for ( let i = 0 ; i < len ; i ++ ) { const JSDocNode = node . comment [ i ] ; switch ( JSDocNode . kind ) { case SyntaxKind . JSDocComment : rawDescription += JSDocNode . comment ; break ; case SyntaxKind . JSDocText : rawDescription += JSDocNode . text ; break ; case SyntaxKind . JSDocLink : if ( JSDocNode . name ) { let text = JSDocNode . name . escapedText ; if ( text === undefined && JSDocNode . name . left && JSDocNode . name . right ) { text = JSDocNode . name . left . escapedText + '.' + JSDocNode . name . right . escapedText ; } rawDescription += JSDocNode . text + '{@link ' + text + '}' ; } break ; default : break ; } } } } return rawDescription ; } }