Exit Full View

Cavern Quest 2 / build / js / node_modules / karma-sourcemap-loader / index.js

//@ts-check
const fs = require('graceful-fs');
const path = require('path');

const SOURCEMAP_URL_REGEX = /^\/\/#\s*sourceMappingURL=/;
const CHARSET_REGEX = /^;charset=([^;]+);/;

/**
 * @param {*} logger
 * @param {karmaSourcemapLoader.Config} config
 * @returns {karmaSourcemapLoader.Preprocessor}
 */
function createSourceMapLocatorPreprocessor(logger, config) {
  const options = (config && config.sourceMapLoader) || {};
  const remapPrefixes = options.remapPrefixes;
  const remapSource = options.remapSource;
  const useSourceRoot = options.useSourceRoot;
  const onlyWithURL = options.onlyWithURL;
  const strict = options.strict;
  const needsUpdate = remapPrefixes || remapSource || useSourceRoot;
  const log = logger.create('preprocessor.sourcemap');

  /**
   * @param {string[]} sources
   */
  function remapSources(sources) {
    const all = sources.length;
    let remapped = 0;
    /** @type {Record<string, boolean>} */
    const remappedPrefixes = {};
    let remappedSource = false;

    /**
     * Replaces source path prefixes using a key:value map
     * @param {string} source
     * @returns {string | undefined}
     */
    function handlePrefixes(source) {
      if (!remapPrefixes) {
        return undefined;
      }

      let sourcePrefix, targetPrefix, target;
      for (sourcePrefix in remapPrefixes) {
        targetPrefix = remapPrefixes[sourcePrefix];
        if (source.startsWith(sourcePrefix)) {
          target = targetPrefix + source.substring(sourcePrefix.length);
          ++remapped;
          // Log only one remapping as an example for each prefix to prevent
          // flood of messages on the console
          if (!remappedPrefixes[sourcePrefix]) {
            remappedPrefixes[sourcePrefix] = true;
            log.debug(' ', source, '>>', target);
          }
          return target;
        }
      }
    }

    // Replaces source paths using a custom function
    /**
     * @param {string} source
     * @returns {string | undefined}
     */
    function handleMapper(source) {
      if (!remapSource) {
        return undefined;
      }

      const target = remapSource(source);
      // Remapping is considered happenned only if the handler returns
      // a non-empty path different from the existing one
      if (target && target !== source) {
        ++remapped;
        // Log only one remapping as an example to prevent flooding the console
        if (!remappedSource) {
          remappedSource = true;
          log.debug(' ', source, '>>', target);
        }
        return target;
      }
    }

    const result = sources.map((rawSource) => {
      const source = rawSource.replace(/\\/g, '/');

      const sourceWithRemappedPrefixes = handlePrefixes(source);
      if (sourceWithRemappedPrefixes) {
        // One remapping is enough; if a prefix was replaced, do not let
        // the handler below check the source path any more
        return sourceWithRemappedPrefixes;
      }

      return handleMapper(source) || source;
    });

    if (remapped) {
      log.debug('  ...');
      log.debug(' ', remapped, 'sources from', all, 'were remapped');
    }

    return result;
  }

  return function karmaSourcemapLoaderPreprocessor(content, file, done) {
    /**
     * Parses a string with source map as JSON and handles errors
     * @param {string} data
     * @returns {karmaSourcemapLoader.SourceMap | false | undefined}
     */
    function parseMap(data) {
      try {
        return JSON.parse(data);
      } catch (err) {
        if (strict) {
          done(new Error('malformed source map for' + file.originalPath + '\nError: ' + err));
          // Returning `false` will make the caller abort immediately
          return false;
        }
        log.warn('malformed source map for', file.originalPath);
        log.warn('Error:', err);
      }
    }

    /**
     * Sets the sourceRoot property to a fixed or computed value
     * @param {karmaSourcemapLoader.SourceMap} sourceMap
     */
    function setSourceRoot(sourceMap) {
      const sourceRoot = typeof useSourceRoot === 'function' ? useSourceRoot(file) : useSourceRoot;
      if (sourceRoot) {
        sourceMap.sourceRoot = sourceRoot;
      }
    }

    /**
     * Performs configured updates of the source map content
     * @param {karmaSourcemapLoader.SourceMap} sourceMap
     */
    function updateSourceMap(sourceMap) {
      if (remapPrefixes || remapSource) {
        sourceMap.sources = remapSources(sourceMap.sources);
      }
      if (useSourceRoot) {
        setSourceRoot(sourceMap);
      }
    }

    /**
     * @param {string} data
     * @returns {void}
     */
    function sourceMapData(data) {
      const sourceMap = parseMap(data);
      if (sourceMap) {
        // Perform the remapping only if there is a configuration for it
        if (needsUpdate) {
          updateSourceMap(sourceMap);
        }
        file.sourceMap = sourceMap;
      } else if (sourceMap === false) {
        return;
      }
      done(content);
    }

    /**
     * @param {string} inlineData
     */
    function inlineMap(inlineData) {
      let charset = 'utf-8';

      if (CHARSET_REGEX.test(inlineData)) {
        const matches = inlineData.match(CHARSET_REGEX);

        if (matches && matches.length === 2) {
          charset = matches[1];
          inlineData = inlineData.slice(matches[0].length - 1);
        }
      }

      if (/^;base64,/.test(inlineData)) {
        // base64-encoded JSON string
        log.debug('base64-encoded source map for', file.originalPath);
        const buffer = Buffer.from(inlineData.slice(';base64,'.length), 'base64');
        //@ts-ignore Assume the parsed charset is supported by Buffer.
        sourceMapData(buffer.toString(charset));
      } else if (inlineData.startsWith(',')) {
        // straight-up URL-encoded JSON string
        log.debug('raw inline source map for', file.originalPath);
        sourceMapData(decodeURIComponent(inlineData.slice(1)));
      } else {
        if (strict) {
          done(new Error('invalid source map in ' + file.originalPath));
        } else {
          log.warn('invalid source map in', file.originalPath);
          done(content);
        }
      }
    }

    /**
     * @param {string} mapPath
     * @param {boolean} optional
     */
    function fileMap(mapPath, optional) {
      fs.readFile(mapPath, function (err, data) {
        // File does not exist
        if (err && err.code === 'ENOENT') {
          if (!optional) {
            if (strict) {
              done(new Error('missing external source map for ' + file.originalPath));
              return;
            } else {
              log.warn('missing external source map for', file.originalPath);
            }
          }
          done(content);
          return;
        }

        // Error while reading the file
        if (err) {
          if (strict) {
            done(
              new Error('reading external source map failed for ' + file.originalPath + '\n' + err)
            );
          } else {
            log.warn('reading external source map failed for', file.originalPath);
            log.warn(err);
            done(content);
          }
          return;
        }

        log.debug('external source map exists for', file.originalPath);
        sourceMapData(data.toString());
      });
    }

    // Remap source paths in a directly served source map
    function convertMap() {
      let sourceMap;
      // Perform the remapping only if there is a configuration for it
      if (needsUpdate) {
        log.debug('processing source map', file.originalPath);
        sourceMap = parseMap(content);
        if (sourceMap) {
          updateSourceMap(sourceMap);
          content = JSON.stringify(sourceMap);
        } else if (sourceMap === false) {
          return;
        }
      }
      done(content);
    }

    if (file.path.endsWith('.map')) {
      return convertMap();
    }

    const lines = content.split(/\n/);
    let lastLine = lines.pop();
    while (typeof lastLine === 'string' && /^\s*$/.test(lastLine)) {
      lastLine = lines.pop();
    }

    const mapUrl =
      lastLine && SOURCEMAP_URL_REGEX.test(lastLine) && lastLine.replace(SOURCEMAP_URL_REGEX, '');

    if (!mapUrl) {
      if (onlyWithURL) {
        done(content);
      } else {
        fileMap(file.path + '.map', true);
      }
    } else if (/^data:application\/json/.test(mapUrl)) {
      inlineMap(mapUrl.slice('data:application/json'.length));
    } else {
      fileMap(path.resolve(path.dirname(file.path), mapUrl), false);
    }
  };
}

createSourceMapLocatorPreprocessor.$inject = ['logger', 'config'];

// PUBLISH DI MODULE
module.exports = {
  'preprocessor:sourcemap': ['factory', createSourceMapLocatorPreprocessor],
};