import * as fs from 'fs-extra' ; import * as _ from 'lodash' ; import * as path from 'path' ; import { ts } from 'ts-morph' ; import { LinkParser } from './link-parser' ; import { logger } from './logger' ; import { AngularLifecycleHooks } from './angular-lifecycles-hooks' ; import { kindToType } from './kind-to-type' ; import { JsdocParserUtil } from './jsdoc-parser.util' ; const getCurrentDirectory = ts . sys . getCurrentDirectory ; const useCaseSensitiveFileNames = ts . sys . useCaseSensitiveFileNames ; const newLine = ts . sys . newLine ; const { marked } = require ( 'marked' ) ; export function getNewLine ( ) { return newLine ; } export function cleanNameWithoutSpaceAndToLowerCase ( name ) { return name . toLowerCase ( ) . replace ( / /g , '-' ) ; } export function getCanonicalFileName ( fileName ) { return useCaseSensitiveFileNames ? fileName : fileName . toLowerCase ( ) ; } export const formatDiagnosticsHost = { getCurrentDirectory , getCanonicalFileName , getNewLine } ; export function markedtags ( tags ) { const jsdocParserUtil = new JsdocParserUtil ( ) ; let mtags = tags ; _ . forEach ( mtags , tag => { const rawComment = jsdocParserUtil . parseJSDocNode ( tag ) ; tag . comment = marked ( LinkParser . resolveLinks ( rawComment ) ) ; } ) ; return mtags ; } export function mergeTagsAndArgs ( args , jsdoctags ? ) { let margs = _ . cloneDeep ( args ) ; _ . forEach ( margs , arg => { arg . tagName = { text : 'param' } ; if ( jsdoctags ) { _ . forEach ( jsdoctags , jsdoctag => { if ( jsdoctag . name && jsdoctag . name . text === arg . name ) { arg . tagName = jsdoctag . tagName ; arg . name = jsdoctag . name ; arg . comment = jsdoctag . comment ; arg . typeExpression = jsdoctag . typeExpression ; } } ) ; } } ) ; if ( jsdoctags ) { _ . forEach ( jsdoctags , jsdoctag => { if ( jsdoctag . tagName && ( jsdoctag . tagName . text === 'example' || jsdoctag . tagName . text === 'private' ) ) { margs . push ( { tagName : jsdoctag . tagName , comment : jsdoctag . comment } ) ; } if ( jsdoctag . tagName && ( jsdoctag . tagName . text === 'returns' || jsdoctag . tagName . text === 'return' ) ) { let ret = { tagName : jsdoctag . tagName , comment : jsdoctag . comment } ; if ( jsdoctag . typeExpression && jsdoctag . typeExpression . type ) { ret . returnType = kindToType ( jsdoctag . typeExpression . type . kind ) ; } margs . push ( ret ) ; } } ) ; } return margs ; } export function readConfig ( configFile ) { let result = ts . readConfigFile ( configFile , ts . sys . readFile ) ; if ( result . error ) { let message = ts . formatDiagnostics ( [ result . error ] , formatDiagnosticsHost ) ; throw new Error ( message ) ; } return result . config ; } export function stripBom ( source ) { if ( source . charCodeAt ( 0 ) === 0xfeff ) { return source . slice ( 1 ) ; } return source ; } export function hasBom ( source ) { return source . charCodeAt ( 0 ) === 0xfeff ; } export function handlePath ( files , cwd ) { let _files = files ; let i = 0 ; let len = files . length ; for ( i ; i < len ; i ++ ) { if ( files [ i ] . indexOf ( cwd ) === - 1 ) { files [ i ] = path . resolve ( cwd + path . sep + files [ i ] ) ; } } return _files ; } export function cleanLifecycleHooksFromMethods ( methods ) { let result = [ ] ; if ( typeof methods !== 'undefined' ) { let i = 0 ; let len = methods . length ; for ( i ; i < len ; i ++ ) { if ( ! ( methods [ i ] . name in AngularLifecycleHooks ) ) { result . push ( methods [ i ] ) ; } } } return result ; } export function cleanSourcesForWatch ( list ) { return list . filter ( element => { if ( fs . existsSync ( process . cwd ( ) + path . sep + element ) ) { return element ; } } ) ; } export function getNamesCompareFn ( name ? ) { /**
     * Copyright https://github.com/ng-bootstrap/ng-bootstrap
     */ name = name || 'name' ; const t = ( a , b ) => { if ( a [ name ] ) { return a [ name ] . localeCompare ( b [ name ] ) ; } else { return 0 ; } } ; return t ; } export function isIgnore ( member ) { if ( member . jsDoc ) { for ( const doc of member . jsDoc ) { if ( doc . tags ) { for ( const tag of doc . tags ) { if ( tag . tagName . text . indexOf ( 'ignore' ) > - 1 ) { return true ; } } } } } return false ; } if ( ! Array . prototype . includes ) { Object . defineProperty ( Array . prototype , 'includes' , { value : function ( searchElement , fromIndex ) { if ( this == null ) { throw new TypeError ( '"this" is null or not defined' ) ; } let o = Object ( this ) ; let len = o . length >>> 0 ; if ( len === 0 ) { return false ; } let n = fromIndex | 0 ; let k = Math . max ( n >= 0 ? n : len - Math . abs ( n ) , 0 ) ; function sameValueZero ( x , y ) { return ( x === y || ( typeof x === 'number' && typeof y === 'number' && isNaN ( x ) && isNaN ( y ) ) ) ; } while ( k < len ) { if ( sameValueZero ( o [ k ] , searchElement ) ) { return true ; } k ++ ; } return false ; } } ) ; } export function findMainSourceFolder ( files ) { let mainFolder = '' ; let mainFolderCount = 0 ; let rawFolders = files . map ( filepath => { let shortPath = filepath . replace ( process . cwd ( ) + path . sep , '' ) ; return path . dirname ( shortPath ) ; } ) ; let folders = { } ; rawFolders = _ . uniq ( rawFolders ) ; for ( let i = 0 ; i < rawFolders . length ; i ++ ) { let sep = rawFolders [ i ] . split ( path . sep ) ; sep . forEach ( folder => { if ( folders [ folder ] ) { folders [ folder ] += 1 ; } else { folders [ folder ] = 1 ; } } ) ; } for ( let f in folders ) { if ( folders [ f ] > mainFolderCount ) { mainFolderCount = folders [ f ] ; mainFolder = f ; } } return mainFolder ; } export function compilerHost ( transpileOptions ) { const inputFileName = transpileOptions . fileName || ( transpileOptions . jsx ? 'module.tsx' : 'module.ts' ) ; const toReturn = { getSourceFile : ( fileName ) => { if ( fileName . lastIndexOf ( '.ts' ) !== - 1 || fileName . lastIndexOf ( '.js' ) !== - 1 ) { if ( fileName === 'lib.d.ts' ) { return undefined ; } if ( fileName . substr ( - 5 ) === '.d.ts' ) { return undefined ; } if ( path . isAbsolute ( fileName ) === false ) { fileName = path . join ( transpileOptions . tsconfigDirectory , fileName ) ; } if ( ! fs . existsSync ( fileName ) ) { return undefined ; } let libSource = '' ; try { libSource = fs . readFileSync ( fileName ) . toString ( ) ; if ( hasBom ( libSource ) ) { libSource = stripBom ( libSource ) ; } } catch ( e ) { logger . debug ( e , fileName ) ; } return ts . createSourceFile ( fileName , libSource , transpileOptions . target , false ) ; } return undefined ; } , writeFile : ( name , text ) => { } , getDefaultLibFileName : ( ) => 'lib.d.ts' , useCaseSensitiveFileNames : ( ) => false , getCanonicalFileName : fileName => fileName , getCurrentDirectory : ( ) => '' , getNewLine : ( ) => '\n' , fileExists : ( fileName ) : boolean => fileName === inputFileName , readFile : ( ) => '' , directoryExists : ( ) => true , getDirectories : ( ) => [ ] } ; return toReturn ; } export function detectIndent ( str , count ) { let stripIndent = ( stripedString ) => { const match = stripedString . match ( /^[ \t]*(?=\S)/gm ) ; if ( ! match ) { return stripedString ; } const indent = Math . min . apply ( Math , match . map ( x => x . length ) ) ; const re = new RegExp ( `^[ \\t]{${ indent }}` , 'gm' ) ; return indent > 0 ? stripedString . replace ( re , '' ) : stripedString ; } ; let repeating = ( n , repeatString ) => { repeatString = repeatString === undefined ? ' ' : repeatString ; if ( typeof repeatString !== 'string' ) { throw new TypeError ( `Expected \`input\` to be a \`string\`, got \`${ typeof repeatString }\`` ) ; } if ( n < 0 ) { throw new TypeError ( `Expected \`count\` to be a positive finite number, got \`${ n }\`` ) ; } let ret = '' ; do { if ( n & 1 ) { ret += repeatString ; } repeatString += repeatString ; } while ( ( n >>= 1 ) ) ; return ret ; } ; let indentString = ( indentedString , indentCount ) => { let indent = ' ' ; indentCount = indentCount === undefined ? 1 : indentCount ; if ( typeof indentedString !== 'string' ) { throw new TypeError ( `Expected \`input\` to be a \`string\`, got \`${ typeof indentedString }\`` ) ; } if ( typeof indentCount !== 'number' ) { throw new TypeError ( `Expected \`count\` to be a \`number\`, got \`${ typeof indentCount }\`` ) ; } if ( typeof indent !== 'string' ) { throw new TypeError ( `Expected \`indent\` to be a \`string\`, got \`${ typeof indent }\`` ) ; } if ( indentCount === 0 ) { return indentedString ; } indent = indentCount > 1 ? repeating ( indentCount , indent ) : indent ; return indentedString . replace ( /^(?!\s*$)/gm , indent ) ; } ; return indentString ( stripIndent ( str ) , count || 0 ) ; } const IGNORED_DIRECTORIES = [ '.git' , 'node_modules' ] ; export function ignoreDirectory ( dir ) { let base = path . basename ( dir ) ; if ( IGNORED_DIRECTORIES . includes ( base ) ) return true ; try { fs . accessSync ( dir , fs . constants . W_OK ) ; } catch ( err ) { logger . warn ( 'Ignoring inaccessible folder' , dir ) ; return true ; } return false ; }