Home Reference Source Test Repository

src/PathResolver.js

import path from 'path';
import os   from 'os';

/**
 * Provides a file path resolver that is NPM module aware.
 *
 * @example
 * const pathResolver = new PathResolver('.', 'foo/bar.js', 'foo-bar', 'foo/bar.js');
 * pathResolver.importPath;            // 'foo-bar'
 * pathResolver.filePath;              // 'foo/bar.js'
 * pathResolver.resolve('./baz.js');   // 'foo/baz.js'
 */
export default class PathResolver
{
   /**
    * Instantiate PathResolver.
    *
    * @param {string} rootPath - root directory path.
    *
    * @param {string} filePath - relative file path from root directory path.
    *
    * @param {string} [packageName] - npm package name.
    *
    * @param {string} [mainFilePath] - npm main file path.
    */
   constructor(rootPath, filePath, packageName = void 0, mainFilePath = void 0)
   {
      this.setPathData(rootPath, filePath, packageName, mainFilePath);
   }

   /**
    * Gets import path that is considered package name or main file and path prefix.
    *
    * @returns {string}
    */
   get importPath()
   {
      const relativeFilePath = this.filePath;

      if (this._mainFilePath === path.resolve(relativeFilePath))
      {
         return this._packageName;
      }

      let filePath;

      // If the relative file path starts with an actual relative path outside of where TJSDoc is executed then
      // use the relative file path as is...
      if ((/^(?:\.\.)+/).test(relativeFilePath))
      {
         filePath = relativeFilePath;
      }
      else
      {
         // If the package name is available then construct the relative import to the package / NPM module.
         if (this._packageName)
         {
            filePath = path.normalize(`${this._packageName}${path.sep}${relativeFilePath}`);
         }
         else
         {
            // Consider the relative path as local source.
            filePath = `./${relativeFilePath}`;
         }
      }

      return this._slash(filePath);
   }

   /**
    * Gets the absolute file path.
    *
    * @returns {string}
    */
   get absolutePath()
   {
      return this._slash(this._filePath);
   }

   /**
    * Gets the file path that is the relative path from the root dir.
    *
    * @returns {string}
    */
   get filePath()
   {
      const relativeFilePath = path.relative(this._rootPath, this._filePath);

      return this._slash(relativeFilePath);
   }

   /**
    * Resolves the given file path with the data initialized in PathResolver.
    *
    * @param {string} relativePath - relative path on this file.
    *
    * @returns {string}
    */
   resolve(relativePath)
   {
      if (typeof relativePath !== 'string') { throw new TypeError(`'relativePath' is not a 'string'.`); }

      const selfDirPath = path.dirname(this._filePath);
      const resolvedPath = path.resolve(selfDirPath, relativePath);
      const resolvedRelativePath = path.relative(this._rootPath, resolvedPath);

      return this._slash(resolvedRelativePath);
   }

   /**
    * Resolve absolute path given the file path on this file.
    *
    * @param {string} relativePath - relative path on this file.
    *
    * @returns {string}
    */
   resolveAbsolutePath(relativePath)
   {
      if (typeof relativePath !== 'string') { throw new TypeError(`'relativePath' is not a 'string'.`); }

      const selfDirPath = path.dirname(this._filePath);
      const resolvedPath = path.resolve(selfDirPath, relativePath);

      return this._slash(resolvedPath);
   }

   /**
    * Sets the given path data to this PathResolver.
    *
    * @param {string} rootPath - root directory path.
    *
    * @param {string} filePath - relative file path from root directory path.
    *
    * @param {string} [packageName] - npm package name.
    *
    * @param {string} [mainFilePath] - npm main file path.
    */
   setPathData(rootPath, filePath, packageName = void 0, mainFilePath = void 0)
   {
      if (typeof rootPath !== 'string') { throw new TypeError(`'rootPath' is not a 'string'.`); }
      if (typeof filePath !== 'string') { throw new TypeError(`'filePath' is not a 'string'.`); }

      if (typeof packageName !== 'undefined' && typeof packageName !== 'string')
      {
         throw new TypeError(`'packageName' is not a 'string'.`);
      }

      if (typeof mainFilePath !== 'undefined' && typeof mainFilePath !== 'string')
      {
         throw new TypeError(`'mainFilePath' is not a 'string'.`);
      }

      /**
       * @type {string}
       */
      this._rootPath = path.resolve(rootPath);

      /**
       * @type {string}
       */
      this._filePath = path.resolve(filePath);

      /**
       * @type {NPMPackageObject}
       */
      this._packageName = packageName;

      if (mainFilePath)
      {
         /**
          * @type {string}
          */
         this._mainFilePath = path.resolve(mainFilePath);
      }
   }

   /**
    * Converts 'back slash' to 'slash' as necessary if the OS platform is windows.
    *
    * @param {string} filePath - Target file path.
    *
    * @returns {string} converted path.
    * @private
    */
   _slash(filePath)
   {
      if (os.platform() === 'win32') { filePath = filePath.replace(/\\/g, '/'); }

      return filePath;
   }
}

/**
 * Wires up PathResolver on the plugin eventbus.
 *
 * @param {PluginEvent} ev - The plugin event.
 *
 * @ignore
 */
export function onPluginLoad(ev)
{
   const eventbus = ev.eventbus;

   eventbus.on('tjsdoc:create:path:resolver', (rootPath, filePath, packageName = void 0, mainFilePath = void 0) =>
   {
      return new PathResolver(rootPath, filePath, packageName, mainFilePath);
   });
}