@@ -4,20 +4,13 @@ const fs = require('fs');
44const env = require ( 'jsdoc/env' ) ; // eslint-disable-line import/no-unresolved
55const addInherited = require ( 'jsdoc/augment' ) . addInherited ; // eslint-disable-line import/no-unresolved
66
7- const config = env . conf . typescript ;
8- if ( ! config ) {
9- throw new Error (
10- 'Configuration "typescript" for jsdoc-plugin-typescript missing.'
11- ) ;
12- }
13- if ( ! ( 'moduleRoot' in config ) ) {
14- throw new Error (
15- 'Configuration "typescript.moduleRoot" for jsdoc-plugin-typescript missing.'
16- ) ;
17- }
18- const moduleRoot = config . moduleRoot ;
19- const moduleRootAbsolute = path . join ( process . cwd ( ) , moduleRoot ) ;
20- if ( ! fs . existsSync ( moduleRootAbsolute ) ) {
7+ const config = env . conf ;
8+ const moduleRoot = config . typescript ? config . typescript . moduleRoot : undefined ;
9+ const moduleRootAbsolute = moduleRoot
10+ ? path . join ( process . cwd ( ) , moduleRoot )
11+ : undefined ;
12+
13+ if ( moduleRootAbsolute && ! fs . existsSync ( moduleRootAbsolute ) ) {
2114 throw new Error (
2215 'Directory "' +
2316 moduleRootAbsolute +
@@ -30,31 +23,55 @@ const importRegEx =
3023const typedefRegEx = / @ t y p e d e f \{ [ ^ \} ] * \} ( \S + ) / g;
3124const noClassdescRegEx = / @ ( t y p e d e f | m o d u l e | t y p e ) / ;
3225const extensionReplaceRegEx = / \. m ? j s $ / ;
26+ const extensionEnsureRegEx = / ( \. j s ) ? $ / ;
3327const slashRegEx = / \\ / g;
3428const leadingPathSegmentRegEx = / ^ ( .? .[ / \\ ] ) + / ;
3529
3630const moduleInfos = { } ;
3731const fileNodes = { } ;
3832
39- function getExtension ( absolutePath ) {
40- return extensionReplaceRegEx . test ( absolutePath )
41- ? extensionReplaceRegEx . exec ( absolutePath ) [ 0 ]
42- : '.js' ;
33+ let inferredModuleRoot ;
34+
35+ function getInferredModuleRoot ( ) {
36+ if ( inferredModuleRoot ) {
37+ return inferredModuleRoot ;
38+ }
39+
40+ inferredModuleRoot = env . sourceFiles . reduce ( ( nearestAncestor , curr , i ) => {
41+ if ( curr . startsWith ( nearestAncestor ) || i === 0 ) {
42+ return nearestAncestor ;
43+ }
44+
45+ const currParts = curr . split ( path . sep ) ;
46+ const nearestParts = nearestAncestor . split ( path . sep ) ;
47+
48+ for ( let i = 0 ; i < currParts . length ; ++ i ) {
49+ if ( currParts [ i ] !== nearestParts [ i ] ) {
50+ return currParts . slice ( 0 , i ) . join ( path . sep ) ;
51+ }
52+ }
53+ } , path . dirname ( env . sourceFiles [ 0 ] ) ) ;
54+
55+ return inferredModuleRoot ;
4356}
4457
45- function getModuleInfo ( moduleId , extension , parser ) {
46- if ( ! moduleInfos [ moduleId ] ) {
47- if ( ! fileNodes [ moduleId ] ) {
48- const absolutePath = path . join ( moduleRootAbsolute , moduleId + extension ) ;
49- if ( ! fs . existsSync ( absolutePath ) ) {
58+ function getModuleInfo ( modulePath , parser ) {
59+ if ( ! moduleInfos [ modulePath ] ) {
60+ if ( ! fileNodes [ modulePath ] ) {
61+ if ( ! fs . existsSync ( modulePath ) ) {
5062 return null ;
5163 }
52- const file = fs . readFileSync ( absolutePath , 'UTF-8' ) ;
53- fileNodes [ moduleId ] = parser . astBuilder . build ( file , absolutePath ) ;
64+
65+ const file = fs . readFileSync ( modulePath , 'UTF-8' ) ;
66+
67+ fileNodes [ modulePath ] = parser . astBuilder . build ( file , modulePath ) ;
5468 }
55- moduleInfos [ moduleId ] = { namedExports : { } } ;
56- const moduleInfo = moduleInfos [ moduleId ] ;
57- const node = fileNodes [ moduleId ] ;
69+
70+ moduleInfos [ modulePath ] = { namedExports : { } } ;
71+
72+ const moduleInfo = moduleInfos [ modulePath ] ;
73+ const node = fileNodes [ modulePath ] ;
74+
5875 if ( node . program && node . program . body ) {
5976 const classDeclarations = { } ;
6077 const nodes = node . program . body ;
@@ -77,22 +94,57 @@ function getModuleInfo(moduleId, extension, parser) {
7794 }
7895 }
7996 }
80- return moduleInfos [ moduleId ] ;
97+
98+ return moduleInfos [ modulePath ] ;
8199}
82100
83- function getDefaultExportName ( moduleId , parser ) {
84- return getModuleInfo ( moduleId , parser ) . defaultExport ;
101+ function getDefaultExportName ( modulePath ) {
102+ return getModuleInfo ( modulePath ) . defaultExport ;
85103}
86104
87- function getDelimiter ( moduleId , symbol , parser ) {
88- return getModuleInfo ( moduleId , parser ) . namedExports [ symbol ] ? '.' : '~' ;
105+ function getDelimiter ( modulePath , symbol ) {
106+ return getModuleInfo ( modulePath ) . namedExports [ symbol ] ? '.' : '~' ;
89107}
90108
91109function getModuleId ( modulePath ) {
92- return path
93- . relative ( moduleRootAbsolute , modulePath )
94- . replace ( extensionReplaceRegEx , '' )
95- . replace ( leadingPathSegmentRegEx , '' ) ;
110+ // Use moduleRoot if set
111+ if ( moduleRootAbsolute ) {
112+ return path
113+ . relative ( moduleRootAbsolute , modulePath )
114+ . replace ( extensionReplaceRegEx , '' )
115+ . replace ( leadingPathSegmentRegEx , '' ) ;
116+ }
117+
118+ // Search for explicit module id
119+ if ( fileNodes [ modulePath ] ) {
120+ for ( const comment of fileNodes [ modulePath ] . comments ) {
121+ if ( ! / @ m o d u l e (? = \s ) / . test ( comment . value ) ) {
122+ continue ;
123+ }
124+
125+ const explicitModuleId = comment . value
126+ . split ( / @ m o d u l e (? = \s ) / ) [ 1 ]
127+ . split ( / \n + \s * \* \s * @ \w + / ) [ 0 ] // Split before the next tag
128+ . replace ( / \n + \s * \* | \{ [ ^ \} ] * \} / g, '' ) // Remove new lines with asterisks, and type annotations
129+ . trim ( ) ;
130+
131+ if ( explicitModuleId ) {
132+ return explicitModuleId ;
133+ }
134+ }
135+ }
136+
137+ if ( getInferredModuleRoot ( ) ) {
138+ return path
139+ . relative ( inferredModuleRoot , modulePath )
140+ . replace ( extensionReplaceRegEx , '' ) ;
141+ }
142+
143+ throw new Error ( `Unable to resolve module id for file ${ modulePath } .` ) ;
144+ }
145+
146+ function withJsExt ( filePath ) {
147+ return filePath . replace ( extensionEnsureRegEx , '.js' ) ;
96148}
97149
98150exports . defineTags = function ( dictionary ) {
@@ -146,7 +198,7 @@ exports.defineTags = function (dictionary) {
146198exports . astNodeVisitor = {
147199 visitNode : function ( node , e , parser , currentSourceName ) {
148200 if ( node . type === 'File' ) {
149- fileNodes [ getModuleId ( currentSourceName ) ] = node ;
201+ fileNodes [ currentSourceName ] = node ;
150202 const identifiers = { } ;
151203 if ( node . program && node . program . body ) {
152204 const nodes = node . program . body ;
@@ -263,19 +315,18 @@ exports.astNodeVisitor = {
263315 if ( identifier ) {
264316 const absolutePath = path . resolve (
265317 path . dirname ( currentSourceName ) ,
266- identifier . value
318+ withJsExt ( identifier . value )
267319 ) ;
268- // default to js extension since .js extention is assumed implicitly
269- const extension = getExtension ( absolutePath ) ;
270- const moduleId = getModuleId ( absolutePath ) ;
271320
272- if ( getModuleInfo ( moduleId , extension , parser ) ) {
321+ if ( getModuleInfo ( absolutePath , parser ) ) {
322+ const moduleId = getModuleId ( absolutePath ) ;
323+
273324 const exportName = identifier . defaultImport
274- ? getDefaultExportName ( moduleId , parser )
325+ ? getDefaultExportName ( absolutePath )
275326 : node . superClass . name ;
276327 const delimiter = identifier . defaultImport
277328 ? '~'
278- : getDelimiter ( moduleId , exportName , parser ) ;
329+ : getDelimiter ( absolutePath , exportName ) ;
279330 lines [ lines . length - 2 ] =
280331 ' * @extends ' +
281332 `module:${ moduleId . replace ( slashRegEx , '/' ) } ${
@@ -332,21 +383,18 @@ exports.astNodeVisitor = {
332383 lastImportPath = importExpression ;
333384 const rel = path . resolve (
334385 path . dirname ( currentSourceName ) ,
335- importSource
386+ withJsExt ( importSource )
336387 ) ;
337- // default to js extension since .js extention is assumed implicitly
338- const extension = getExtension ( rel ) ;
339- const moduleId = getModuleId ( rel ) ;
340388
341- if ( getModuleInfo ( moduleId , extension , parser ) ) {
389+ if ( getModuleInfo ( rel , parser ) ) {
390+ const moduleId = getModuleId ( rel ) ;
391+
342392 const name =
343393 exportName === 'default'
344- ? getDefaultExportName ( moduleId , parser )
394+ ? getDefaultExportName ( rel )
345395 : exportName ;
346396 const delimiter =
347- exportName === 'default'
348- ? '~'
349- : getDelimiter ( moduleId , name , parser ) ;
397+ exportName === 'default' ? '~' : getDelimiter ( rel , name ) ;
350398 replacement = `module:${ moduleId . replace ( slashRegEx , '/' ) } ${
351399 name ? delimiter + name : ''
352400 } `;
@@ -391,18 +439,18 @@ exports.astNodeVisitor = {
391439 const identifier = identifiers [ key ] ;
392440 const absolutePath = path . resolve (
393441 path . dirname ( currentSourceName ) ,
394- identifier . value
442+ withJsExt ( identifier . value )
395443 ) ;
396- // default to js extension since .js extention is assumed implicitly
397- const extension = getExtension ( absolutePath ) ;
398- const moduleId = getModuleId ( absolutePath ) ;
399- if ( getModuleInfo ( moduleId , extension , parser ) ) {
444+
445+ if ( getModuleInfo ( absolutePath , parser ) ) {
446+ const moduleId = getModuleId ( absolutePath ) ;
447+
400448 const exportName = identifier . defaultImport
401- ? getDefaultExportName ( moduleId , parser )
449+ ? getDefaultExportName ( absolutePath )
402450 : key ;
403451 const delimiter = identifier . defaultImport
404452 ? '~'
405- : getDelimiter ( moduleId , exportName , parser ) ;
453+ : getDelimiter ( absolutePath , exportName ) ;
406454 const replacement = `module:${ moduleId . replace (
407455 slashRegEx ,
408456 '/'
0 commit comments