@@ -15,8 +15,9 @@ if (!fs.existsSync(moduleRootAbsolute)) {
1515 throw new Error ( 'Directory "' + moduleRootAbsolute + '" does not exist. Check the "typescript.moduleRoot" config option for jsdoc-plugin-typescript' ) ;
1616}
1717
18- const importRegEx = / ( t y p e o f ) ? i m p o r t \( " ( [ ^ " ] * ) " \) \. ( [ ^ \. \| \} > < , \) = \n ] * ) ( [ \. \| \} > < , \) = \n ] ) / g;
18+ const importRegEx = / i m p o r t \( " ( [ ^ " ] * ) " \) \. ( [ ^ \. \| \} > < , \) = \n ] * ) ( [ \. \| \} > < , \) = \n ] ) / g;
1919const typedefRegEx = / @ t y p e d e f \{ [ ^ \} ] * \} ( [ ^ \r ? \n ? ] * ) / ;
20+ const noClassdescRegEx = / @ ( t y p e d e f | m o d u l e | t y p e ) / ;
2021
2122const moduleInfos = { } ;
2223const fileNodes = { } ;
@@ -98,21 +99,50 @@ exports.astNodeVisitor = {
9899 } ;
99100 }
100101
101- // Add class inheritance information because JSDoc does not honor
102- // the ES6 class's `extends` keyword
103- if ( node . superClass && node . leadingComments ) {
104- const leadingComment = node . leadingComments [ node . leadingComments . length - 1 ] ;
105- const lines = leadingComment . value . split ( / \r ? \n / ) ;
106- lines . push ( lines [ lines . length - 1 ] ) ;
107- const identifier = identifiers [ node . superClass . name ] ;
108- if ( identifier ) {
109- const absolutePath = path . resolve ( path . dirname ( currentSourceName ) , identifier . value ) ;
110- const moduleId = path . relative ( path . join ( process . cwd ( ) , moduleRoot ) , absolutePath ) . replace ( / \. j s $ / , '' ) ;
111- const exportName = identifier . defaultImport ? getDefaultExportName ( moduleId , parser ) : node . superClass . name ;
112- const delimiter = identifier . defaultImport ? '~' : getDelimiter ( moduleId , exportName , parser ) ;
113- lines [ lines . length - 2 ] = ' * @extends ' + `module:${ moduleId } ${ exportName ? delimiter + exportName : '' } ` ;
114- } else {
115- lines [ lines . length - 2 ] = ' * @extends ' + node . superClass . name ;
102+ if ( ! node . leadingComments ) {
103+ node . leadingComments = [ ] ;
104+ // Restructure named exports of classes so only the class, but not
105+ // the export are documented
106+ if ( node . parent && node . parent . type === 'ExportNamedDeclaration' && node . parent . leadingComments ) {
107+ for ( let i = node . parent . leadingComments . length - 1 ; i >= 0 ; -- i ) {
108+ const comment = node . parent . leadingComments [ i ] ;
109+ if ( comment . value . indexOf ( '@classdesc' ) !== - 1 || ! noClassdescRegEx . test ( comment . value ) ) {
110+ node . leadingComments . push ( comment ) ;
111+ node . parent . leadingComments . splice ( i , 1 ) ;
112+ const ignore = parser . astBuilder . build ( '/** @ignore */' ) . comments [ 0 ] ;
113+ node . parent . leadingComments . push ( ignore ) ;
114+ }
115+ }
116+ }
117+ }
118+ const leadingComments = node . leadingComments ;
119+ if ( leadingComments . length === 0 || leadingComments [ leadingComments . length - 1 ] . value . indexOf ( '@classdesc' ) === - 1 &&
120+ noClassdescRegEx . test ( leadingComments [ leadingComments . length - 1 ] . value ) ) {
121+ // Create a suitable comment node if we don't have one on the class yet
122+ const comment = parser . astBuilder . build ( '/**\n */' , 'helper' ) . comments [ 0 ] ;
123+ node . leadingComments . push ( comment ) ;
124+ }
125+ const leadingComment = leadingComments [ node . leadingComments . length - 1 ] ;
126+ const lines = leadingComment . value . split ( / \r ? \n / ) ;
127+ // Add @classdesc to make JSDoc show the class description
128+ if ( leadingComment . value . indexOf ( '@classdesc' ) === - 1 ) {
129+ lines [ 0 ] += ' @classdesc' ;
130+ }
131+ if ( node . superClass ) {
132+ // Add class inheritance information because JSDoc does not honor
133+ // the ES6 class's `extends` keyword
134+ if ( leadingComment . value . indexOf ( '@extends' ) === - 1 ) {
135+ lines . push ( lines [ lines . length - 1 ] ) ;
136+ const identifier = identifiers [ node . superClass . name ] ;
137+ if ( identifier ) {
138+ const absolutePath = path . resolve ( path . dirname ( currentSourceName ) , identifier . value ) ;
139+ const moduleId = path . relative ( path . join ( process . cwd ( ) , moduleRoot ) , absolutePath ) . replace ( / \. j s $ / , '' ) ;
140+ const exportName = identifier . defaultImport ? getDefaultExportName ( moduleId , parser ) : node . superClass . name ;
141+ const delimiter = identifier . defaultImport ? '~' : getDelimiter ( moduleId , exportName , parser ) ;
142+ lines [ lines . length - 2 ] = ' * @extends ' + `module:${ moduleId } ${ exportName ? delimiter + exportName : '' } ` ;
143+ } else {
144+ lines [ lines . length - 2 ] = ' * @extends ' + node . superClass . name ;
145+ }
116146 }
117147 leadingComment . value = lines . join ( '\n' ) ;
118148 }
@@ -122,27 +152,27 @@ exports.astNodeVisitor = {
122152 }
123153 if ( node . comments ) {
124154 node . comments . forEach ( comment => {
125- //TODO Handle typeof, to indicate that a constructor instead of an
126- // instance is needed.
127- comment . value = comment . value . replace ( / t y p e o f / g , '' ) ;
155+ // Replace typeof Foo with Class<Foo>
156+ comment . value = comment . value . replace ( / t y p e o f ( [ ^ , \| \} \> ] * ) ( [ , \| \} \> ] ) / g , 'Class<$1>$2' ) ;
157+ debugger
128158
129159 // Convert `import("path/to/module").export` to
130160 // `module:path/to/module~Name`
131161 let importMatch ;
132162 while ( ( importMatch = importRegEx . exec ( comment . value ) ) ) {
133163 importRegEx . lastIndex = 0 ;
134164 let replacement ;
135- if ( importMatch [ 2 ] . charAt ( 0 ) !== '.' ) {
165+ if ( importMatch [ 1 ] . charAt ( 0 ) !== '.' ) {
136166 // simplified replacement for external packages
137- replacement = `module:${ importMatch [ 2 ] } ${ importMatch [ 3 ] === 'default' ? '' : '~' + importMatch [ 3 ] } ` ;
167+ replacement = `module:${ importMatch [ 1 ] } ${ importMatch [ 2 ] === 'default' ? '' : '~' + importMatch [ 2 ] } ` ;
138168 } else {
139- const rel = path . resolve ( path . dirname ( currentSourceName ) , importMatch [ 2 ] ) ;
169+ const rel = path . resolve ( path . dirname ( currentSourceName ) , importMatch [ 1 ] ) ;
140170 const importModule = path . relative ( path . join ( process . cwd ( ) , moduleRoot ) , rel ) . replace ( / \. j s $ / , '' ) ;
141- const exportName = importMatch [ 3 ] === 'default' ? getDefaultExportName ( importModule , parser ) : importMatch [ 3 ] ;
142- const delimiter = importMatch [ 3 ] === 'default' ? '~' : getDelimiter ( importModule , exportName , parser ) ;
171+ const exportName = importMatch [ 2 ] === 'default' ? getDefaultExportName ( importModule , parser ) : importMatch [ 2 ] ;
172+ const delimiter = importMatch [ 2 ] === 'default' ? '~' : getDelimiter ( importModule , exportName , parser ) ;
143173 replacement = `module:${ importModule } ${ exportName ? delimiter + exportName : '' } ` ;
144174 }
145- comment . value = comment . value . replace ( importMatch [ 0 ] , replacement + importMatch [ 4 ] ) ;
175+ comment . value = comment . value . replace ( importMatch [ 0 ] , replacement + importMatch [ 3 ] ) ;
146176 }
147177
148178 // Treat `@typedef`s like named exports
@@ -155,7 +185,7 @@ exports.astNodeVisitor = {
155185
156186 // Replace local types with the full `module:` path
157187 Object . keys ( identifiers ) . forEach ( key => {
158- const regex = new RegExp ( `(@fires |[\{<\|,] ?)${ key } ` , 'g' ) ;
188+ const regex = new RegExp ( `(@fires |[\{<\|,] ?!? )${ key } ` , 'g' ) ;
159189 if ( regex . test ( comment . value ) ) {
160190 const identifier = identifiers [ key ] ;
161191 const absolutePath = path . resolve ( path . dirname ( currentSourceName ) , identifier . value ) ;
0 commit comments