@@ -29,6 +29,7 @@ const leadingPathSegmentRegEx = /^(.?.[/\\])+/;
2929
3030const moduleInfos = { } ;
3131const fileNodes = { } ;
32+ const resolvedPathCache = new Set ( ) ;
3233
3334// Without explicit module ids, JSDoc will use the nearest shared parent directory
3435/** @type {string } */
@@ -71,6 +72,10 @@ function getImplicitModuleRoot() {
7172}
7273
7374function getModuleId ( modulePath ) {
75+ if ( moduleInfos [ modulePath ] ) {
76+ return moduleInfos [ modulePath ] . id ;
77+ }
78+
7479 // Use moduleRoot if set
7580 if ( moduleRootAbsolute ) {
7681 return path
@@ -103,13 +108,39 @@ function getModuleId(modulePath) {
103108 . replace ( extensionReplaceRegEx , '' ) ;
104109}
105110
111+ /**
112+ * Checks for the existence of `modulePath`, and if it doesn't exist, checks for `modulePath/index.js`.
113+ * @param {string } from The path to the module.
114+ * @param {string } to The path to the module.
115+ * @return {string | null } The resolved path or null if it can't be resolved.
116+ */
117+ function getResolvedPath ( from , to ) {
118+ let resolvedPath = path . resolve ( from , to ) ;
119+
120+ if ( resolvedPathCache . has ( resolvedPath ) || fs . existsSync ( resolvedPath ) ) {
121+ resolvedPathCache . add ( resolvedPath ) ;
122+
123+ return resolvedPath ;
124+ }
125+
126+ resolvedPath = resolvedPath . replace ( extensionEnsureRegEx , '/index.js' ) ;
127+
128+ if ( resolvedPathCache . has ( resolvedPath ) || fs . existsSync ( resolvedPath ) ) {
129+ resolvedPathCache . add ( resolvedPath ) ;
130+
131+ return resolvedPath ;
132+ }
133+
134+ return null ;
135+ }
136+
106137function getModuleInfo ( modulePath , parser ) {
138+ if ( ! modulePath ) {
139+ return null ;
140+ }
141+
107142 if ( ! moduleInfos [ modulePath ] ) {
108143 if ( ! fileNodes [ modulePath ] ) {
109- if ( ! fs . existsSync ( modulePath ) ) {
110- return null ;
111- }
112-
113144 const file = fs . readFileSync ( modulePath , 'UTF-8' ) ;
114145
115146 fileNodes [ modulePath ] = parser . astBuilder . build ( file , modulePath ) ;
@@ -157,7 +188,7 @@ function getDelimiter(modulePath, symbol) {
157188 return getModuleInfo ( modulePath ) . namedExports [ symbol ] ? '.' : '~' ;
158189}
159190
160- function withJsExt ( filePath ) {
191+ function ensureJsExt ( filePath ) {
161192 return filePath . replace ( extensionEnsureRegEx , '.js' ) ;
162193}
163194
@@ -327,23 +358,24 @@ exports.astNodeVisitor = {
327358 lines . push ( lines [ lines . length - 1 ] ) ;
328359 const identifier = identifiers [ node . superClass . name ] ;
329360 if ( identifier ) {
330- const absolutePath = path . resolve (
361+ const absolutePath = getResolvedPath (
331362 path . dirname ( currentSourceName ) ,
332- withJsExt ( identifier . value )
363+ ensureJsExt ( identifier . value )
333364 ) ;
365+ const moduleInfo = getModuleInfo ( absolutePath , parser ) ;
334366
335- if ( getModuleInfo ( absolutePath , parser ) ) {
336- const moduleId = moduleInfos [ absolutePath ] . id ;
337-
367+ if ( moduleInfo ) {
338368 const exportName = identifier . defaultImport
339369 ? getDefaultExportName ( absolutePath )
340370 : node . superClass . name ;
371+
341372 const delimiter = identifier . defaultImport
342373 ? '~'
343374 : getDelimiter ( absolutePath , exportName ) ;
375+
344376 lines [ lines . length - 2 ] =
345377 ' * @extends ' +
346- `module:${ moduleId . replace ( slashRegEx , '/' ) } ${
378+ `module:${ moduleInfo . id . replace ( slashRegEx , '/' ) } ${
347379 exportName ? delimiter + exportName : ''
348380 } `;
349381 }
@@ -395,23 +427,26 @@ exports.astNodeVisitor = {
395427 replaceAttempt = 0 ;
396428 }
397429 lastImportPath = importExpression ;
398- const rel = path . resolve (
430+
431+ const rel = getResolvedPath (
399432 path . dirname ( currentSourceName ) ,
400- withJsExt ( importSource )
433+ ensureJsExt ( importSource )
401434 ) ;
435+ const moduleInfo = getModuleInfo ( rel , parser ) ;
402436
403- if ( getModuleInfo ( rel , parser ) ) {
404- const moduleId = moduleInfos [ rel ] . id ;
405-
437+ if ( moduleInfo ) {
406438 const name =
407439 exportName === 'default'
408440 ? getDefaultExportName ( rel )
409441 : exportName ;
442+
410443 const delimiter =
411444 exportName === 'default' ? '~' : getDelimiter ( rel , name ) ;
412- replacement = `module:${ moduleId . replace ( slashRegEx , '/' ) } ${
413- name ? delimiter + name : ''
414- } `;
445+
446+ replacement = `module:${ moduleInfo . id . replace (
447+ slashRegEx ,
448+ '/'
449+ ) } ${ name ? delimiter + name : '' } `;
415450 }
416451 }
417452 if ( replacement ) {
@@ -451,24 +486,26 @@ exports.astNodeVisitor = {
451486 function replace ( regex ) {
452487 if ( regex . test ( comment . value ) ) {
453488 const identifier = identifiers [ key ] ;
454- const absolutePath = path . resolve (
489+ const absolutePath = getResolvedPath (
455490 path . dirname ( currentSourceName ) ,
456- withJsExt ( identifier . value )
491+ ensureJsExt ( identifier . value )
457492 ) ;
493+ const moduleInfo = getModuleInfo ( absolutePath , parser ) ;
458494
459- if ( getModuleInfo ( absolutePath , parser ) ) {
460- const moduleId = moduleInfos [ absolutePath ] . id ;
461-
495+ if ( moduleInfo ) {
462496 const exportName = identifier . defaultImport
463497 ? getDefaultExportName ( absolutePath )
464498 : key ;
499+
465500 const delimiter = identifier . defaultImport
466501 ? '~'
467502 : getDelimiter ( absolutePath , exportName ) ;
468- const replacement = `module:${ moduleId . replace (
503+
504+ const replacement = `module:${ moduleInfo . id . replace (
469505 slashRegEx ,
470506 '/'
471507 ) } ${ exportName ? delimiter + exportName : '' } `;
508+
472509 comment . value = comment . value . replace (
473510 regex ,
474511 '@$1' + replacement + '$2'
0 commit comments