@@ -504,55 +504,54 @@ namespace ts.codefix {
504504 return undefined ;
505505 }
506506
507- const indexOfTopNodeModules = moduleFileName . indexOf ( "node_modules" ) ;
508- if ( indexOfTopNodeModules < 0 ) {
507+ const parts = getNodeModulePathParts ( moduleFileName ) ;
508+
509+ if ( ! parts ) {
509510 return undefined ;
510511 }
511512
512513 // Simplify the full file path to something that can be resolved by Node.
513- // First remove the extension
514- let moduleSpecifier = removeFileExtension ( moduleFileName ) ;
514+
515515 // If the module could be imported by a directory name, use that directory's name
516- moduleSpecifier = getDirectoryOrFileName ( moduleSpecifier ) ;
516+ let moduleSpecifier = getDirectoryOrExtensionlessFileName ( moduleFileName ) ;
517517 // Get a path that's relative to node_modules or the importing file's path
518518 moduleSpecifier = getNodeResolvablePath ( moduleSpecifier ) ;
519- // If the module was found in @types , get the actual node package name
519+ // If the module was found in @types , get the actual Node package name
520520 return getPackageNameFromAtTypesDirectory ( moduleSpecifier ) ;
521521
522- function getDirectoryOrFileName ( fullModulePathWithoutExtension : string ) : string {
522+ function getDirectoryOrExtensionlessFileName ( path : string ) : string {
523523 // If the file is the main module, it can be imported by the package name
524- const indexOfLastNodeModules = moduleFileName . lastIndexOf ( "node_modules" ) ;
525- const indexOfSlashAtPackageRoot = moduleFileName . indexOf ( "/" , indexOfLastNodeModules + 13 /* "node_modules\".length */ ) ;
526- const packageRootPath = moduleFileName . substring ( 0 , indexOfSlashAtPackageRoot ) ;
524+ const packageRootPath = path . substring ( 0 , parts . packageRootIndex ) ;
527525 const packageJsonPath = combinePaths ( packageRootPath , "package.json" ) ;
528526 if ( context . host . fileExists ( packageJsonPath ) ) {
529527 const packageJsonContent = JSON . parse ( context . host . readFile ( packageJsonPath ) ) ;
530528 if ( packageJsonContent ) {
531529 const mainFileRelative = packageJsonContent . typings || packageJsonContent . types || packageJsonContent . main ;
532530 if ( mainFileRelative ) {
533531 const mainExportFile = toPath ( mainFileRelative , packageRootPath , getCanonicalFileName ) ;
534- if ( removeFileExtension ( mainExportFile ) === removeFileExtension ( moduleFileName ) ) {
532+ if ( mainExportFile === getCanonicalFileName ( path ) ) {
535533 return packageRootPath ;
536534 }
537535 }
538536 }
539537 }
540538
541- // If the file is index.js, it can be imported by its directory name
542- if ( endsWith ( fullModulePathWithoutExtension , "/index" ) ) {
543- return getDirectoryPath ( fullModulePathWithoutExtension ) ;
539+ // We still have a file name - remove the extension
540+ const fullModulePathWithoutExtension = removeFileExtension ( path ) ;
541+
542+ // If the file is /index, it can be imported by its directory name
543+ if ( getCanonicalFileName ( fullModulePathWithoutExtension . substring ( parts . fileNameIndex ) ) === "/index" ) {
544+ return fullModulePathWithoutExtension . substring ( 0 , parts . fileNameIndex ) ;
544545 }
545546
546547 return fullModulePathWithoutExtension ;
547548 }
548549
549550 function getNodeResolvablePath ( path : string ) : string {
550- const fullPathUptoNodeModules = moduleFileName . substring ( 0 , indexOfTopNodeModules - 1 ) ;
551- if ( sourceDirectory . indexOf ( fullPathUptoNodeModules ) === 0 ) {
552- const indexOfTopPackageName = indexOfTopNodeModules + 13 /* "node_modules\".length */ ;
551+ const basePath = path . substring ( 0 , parts . topLevelNodeModulesIndex ) ;
552+ if ( sourceDirectory . indexOf ( basePath ) === 0 ) {
553553 // if node_modules folder is in this folder or any of its parent folders, no need to keep it.
554- const relativeToTopNodeModules = path . substring ( indexOfTopPackageName ) ;
555- return relativeToTopNodeModules ;
554+ return path . substring ( parts . topLevelPackageNameIndex + 1 ) ;
556555 }
557556 else {
558557 return getRelativePath ( path , sourceDirectory ) ;
@@ -561,6 +560,57 @@ namespace ts.codefix {
561560 }
562561 }
563562
563+ function getNodeModulePathParts ( fullPath : string ) {
564+ // If fullPath can't be valid module file within node_modules, returns undefined.
565+ // Example of expected pattern: /base/path/node_modules/[otherpackage/node_modules/]package/[subdirectory/]file.js
566+ // Returns indices: ^ ^ ^ ^
567+
568+ let topLevelNodeModulesIndex = 0 ;
569+ let topLevelPackageNameIndex = 0 ;
570+ let packageRootIndex = 0 ;
571+ let fileNameIndex = 0 ;
572+
573+ const enum States {
574+ BeforeNodeModules ,
575+ NodeModules ,
576+ PackageContent
577+ }
578+
579+ let partStart = 0 ;
580+ let partEnd = 0 ;
581+ let state = States . BeforeNodeModules ;
582+
583+ while ( partEnd >= 0 ) {
584+ partStart = partEnd ;
585+ partEnd = fullPath . indexOf ( "/" , partStart + 1 ) ;
586+ switch ( state ) {
587+ case States . BeforeNodeModules :
588+ if ( fullPath . indexOf ( "/node_modules/" , partStart ) === partStart ) {
589+ topLevelNodeModulesIndex = partStart ;
590+ topLevelPackageNameIndex = partEnd ;
591+ state = States . NodeModules ;
592+ }
593+ break ;
594+ case States . NodeModules :
595+ packageRootIndex = partEnd ;
596+ state = States . PackageContent ;
597+ break ;
598+ case States . PackageContent :
599+ if ( fullPath . indexOf ( "/node_modules/" , partStart ) === partStart ) {
600+ state = States . NodeModules ;
601+ }
602+ else {
603+ state = States . PackageContent ;
604+ }
605+ break ;
606+ }
607+ }
608+
609+ fileNameIndex = partStart ;
610+
611+ return state > States . NodeModules ? { topLevelNodeModulesIndex, topLevelPackageNameIndex, packageRootIndex, fileNameIndex } : undefined ;
612+ }
613+
564614 function getPathRelativeToRootDirs ( path : string , rootDirs : string [ ] ) {
565615 for ( const rootDir of rootDirs ) {
566616 const relativeName = getRelativePathIfInDirectory ( path , rootDir ) ;
0 commit comments