"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;

function _path() {
  const data = _interopRequireDefault(require("path"));

  _path = function () {
    return data;
  };

  return data;
}

function _nullthrows() {
  const data = _interopRequireDefault(require("nullthrows"));

  _nullthrows = function () {
    return data;
  };

  return data;
}

function _utils() {
  const data = require("@parcel/utils");

  _utils = function () {
    return data;
  };

  return data;
}

function _logger() {
  const data = _interopRequireWildcard(require("@parcel/logger"));

  _logger = function () {
    return data;
  };

  return data;
}

function _sourceMap() {
  const data = require("@parcel/source-map");

  _sourceMap = function () {
    return data;
  };

  return data;
}

function _diagnostic() {
  const data = _interopRequireWildcard(require("@parcel/diagnostic"));

  _diagnostic = function () {
    return data;
  };

  return data;
}

function _crypto() {
  const data = _interopRequireDefault(require("crypto"));

  _crypto = function () {
    return data;
  };

  return data;
}

function _v() {
  const data = _interopRequireDefault(require("v8"));

  _v = function () {
    return data;
  };

  return data;
}

function _Dependency() {
  const data = require("./Dependency");

  _Dependency = function () {
    return data;
  };

  return data;
}

function _ParcelConfig() {
  const data = _interopRequireDefault(require("./ParcelConfig"));

  _ParcelConfig = function () {
    return data;
  };

  return data;
}

function _PathRequest() {
  const data = require("./requests/PathRequest");

  _PathRequest = function () {
    return data;
  };

  return data;
}

function _Asset() {
  const data = require("./public/Asset");

  _Asset = function () {
    return data;
  };

  return data;
}

function _UncommittedAsset() {
  const data = _interopRequireDefault(require("./UncommittedAsset"));

  _UncommittedAsset = function () {
    return data;
  };

  return data;
}

function _assetUtils() {
  const data = require("./assetUtils");

  _assetUtils = function () {
    return data;
  };

  return data;
}

function _summarizeRequest() {
  const data = _interopRequireDefault(require("./summarizeRequest"));

  _summarizeRequest = function () {
    return data;
  };

  return data;
}

function _PluginOptions() {
  const data = _interopRequireDefault(require("./public/PluginOptions"));

  _PluginOptions = function () {
    return data;
  };

  return data;
}

function _constants() {
  const data = require("./constants");

  _constants = function () {
    return data;
  };

  return data;
}

function _utils2() {
  const data = require("./utils");

  _utils2 = function () {
    return data;
  };

  return data;
}

function _buildCache() {
  const data = require("./buildCache");

  _buildCache = function () {
    return data;
  };

  return data;
}

function _InternalConfig() {
  const data = require("./InternalConfig");

  _InternalConfig = function () {
    return data;
  };

  return data;
}

function _Config() {
  const data = _interopRequireDefault(require("./public/Config"));

  _Config = function () {
    return data;
  };

  return data;
}

function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }

// A cache of plugin dependency hashes that we've already sent to the main thread.
// Automatically cleared before each build.
const pluginCache = (0, _buildCache().createBuildCache)();
const invalidatedPlugins = (0, _buildCache().createBuildCache)();

class Transformation {
  constructor({
    report,
    request,
    options,
    config,
    workerApi
  }) {
    _defineProperty(this, "request", void 0);

    _defineProperty(this, "configs", void 0);

    _defineProperty(this, "devDepRequests", void 0);

    _defineProperty(this, "options", void 0);

    _defineProperty(this, "pluginOptions", void 0);

    _defineProperty(this, "workerApi", void 0);

    _defineProperty(this, "parcelConfig", void 0);

    _defineProperty(this, "report", void 0);

    _defineProperty(this, "invalidations", void 0);

    _defineProperty(this, "invalidateOnFileCreate", void 0);

    this.configs = new Map();
    this.parcelConfig = config;
    this.options = options;
    this.report = report;
    this.request = request;
    this.workerApi = workerApi;
    this.invalidations = new Map();
    this.invalidateOnFileCreate = [];
    this.devDepRequests = new Map();
    this.pluginOptions = new (_PluginOptions().default)((0, _utils2().optionsProxy)(this.options, option => {
      let invalidation = {
        type: 'option',
        key: option
      };
      this.invalidations.set((0, _assetUtils().getInvalidationId)(invalidation), invalidation);
    }));
  }

  async run() {
    await _sourceMap().init;
    this.report({
      type: 'buildProgress',
      phase: 'transforming',
      filePath: this.request.filePath
    });
    let asset = await this.loadAsset(); // Load existing sourcemaps

    if (_utils().SOURCEMAP_EXTENSIONS.has(asset.value.type)) {
      try {
        await asset.loadExistingSourcemap();
      } catch (err) {
        _logger().default.verbose([{
          origin: '@parcel/core',
          message: (0, _diagnostic().md)`Could not load existing source map for ${_path().default.relative(this.options.projectRoot, asset.value.filePath)}`,
          filePath: asset.value.filePath
        }, {
          origin: '@parcel/core',
          message: (0, _diagnostic().escapeMarkdown)(err.message),
          filePath: asset.value.filePath
        }]);
      }
    }

    for (let {
      moduleSpecifier,
      resolveFrom
    } of this.request.invalidDevDeps) {
      let key = `${moduleSpecifier}:${resolveFrom}`;

      if (!invalidatedPlugins.has(key)) {
        this.parcelConfig.invalidatePlugin(moduleSpecifier);
        this.options.packageManager.invalidate(moduleSpecifier, resolveFrom);
        invalidatedPlugins.set(key, true);
      }
    }

    let pipeline = await this.loadPipeline(this.request.filePath, asset.value.isSource, asset.value.pipeline);
    let results = await this.runPipelines(pipeline, asset);
    let assets = results.map(a => a.value);
    let configRequests = [...this.configs.values()].filter(config => {
      // No need to send to the graph if there are no invalidations.
      return config.includedFiles.size > 0 || config.invalidateOnFileCreate.length > 0 || config.shouldInvalidateOnStartup;
    }).map(config => ({
      id: config.id,
      includedFiles: config.includedFiles,
      invalidateOnFileCreate: config.invalidateOnFileCreate,
      shouldInvalidateOnStartup: config.shouldInvalidateOnStartup
    }));
    let devDepRequests = [];

    for (let devDepRequest of this.devDepRequests.values()) {
      // If we've already sent a matching transformer + hash to the main thread during this build,
      // there's no need to repeat ourselves.
      let {
        moduleSpecifier,
        resolveFrom,
        hash
      } = devDepRequest;

      if (hash === pluginCache.get(moduleSpecifier)) {
        devDepRequests.push({
          moduleSpecifier,
          resolveFrom,
          hash
        });
      } else {
        pluginCache.set(moduleSpecifier, hash);
        devDepRequests.push(devDepRequest);
      }
    }

    return {
      assets,
      configRequests,
      invalidateOnFileCreate: this.invalidateOnFileCreate,
      invalidations: [...this.invalidations.values()],
      devDepRequests
    };
  }

  async loadAsset() {
    let {
      filePath,
      env,
      code,
      pipeline,
      isSource: isSourceOverride,
      sideEffects,
      query
    } = this.request;
    let {
      content,
      size,
      hash,
      isSource: summarizedIsSource
    } = await (0, _summarizeRequest().default)(this.options.inputFS, {
      filePath,
      code
    }); // Prefer `isSource` originating from the AssetRequest.

    let isSource = isSourceOverride !== null && isSourceOverride !== void 0 ? isSourceOverride : summarizedIsSource; // If the transformer request passed code rather than a filename,
    // use a hash as the base for the id to ensure it is unique.

    let idBase = code != null ? hash : (0, _utils().normalizeSeparators)(_path().default.relative(this.options.projectRoot, filePath));
    return new (_UncommittedAsset().default)({
      idBase,
      value: (0, _assetUtils().createAsset)({
        idBase,
        filePath,
        isSource,
        type: _path().default.extname(filePath).slice(1),
        hash,
        pipeline,
        env,
        query,
        stats: {
          time: 0,
          size
        },
        sideEffects
      }),
      options: this.options,
      content,
      invalidations: this.invalidations,
      fileCreateInvalidations: this.invalidateOnFileCreate
    });
  }

  async runPipelines(pipeline, initialAsset) {
    let initialType = initialAsset.value.type;
    let initialPipelineHash = await this.getPipelineHash(pipeline);
    let initialAssetCacheKey = this.getCacheKey([initialAsset], await (0, _assetUtils().getInvalidationHash)(this.request.invalidations, this.options), // TODO: should be per-pipeline
    initialPipelineHash);
    let initialCacheEntry = await this.readFromCache(initialAssetCacheKey);
    let assets = initialCacheEntry || (await this.runPipeline(pipeline, initialAsset)); // Add dev dep requests for each transformer

    for (let transformer of pipeline.transformers) {
      await this.addDevDependency({
        moduleSpecifier: transformer.name,
        resolveFrom: transformer.resolveFrom
      }, transformer);
    }

    if (!initialCacheEntry) {
      let pipelineHash = await this.getPipelineHash(pipeline);
      let resultCacheKey = this.getCacheKey([initialAsset], await (0, _assetUtils().getInvalidationHash)(assets.flatMap(asset => asset.getInvalidations()), this.options), pipelineHash);
      await this.writeToCache(resultCacheKey, assets, pipelineHash);
    }

    let finalAssets = [];

    for (let asset of assets) {
      let nextPipeline;

      if (asset.value.type !== initialType) {
        nextPipeline = await this.loadNextPipeline({
          filePath: initialAsset.value.filePath,
          isSource: asset.value.isSource,
          newType: asset.value.type,
          newPipeline: asset.value.pipeline,
          currentPipeline: pipeline
        });
      }

      if (nextPipeline) {
        let nextPipelineAssets = await this.runPipelines(nextPipeline, asset);
        finalAssets = finalAssets.concat(nextPipelineAssets);
      } else {
        finalAssets.push(asset);
      }
    }

    return finalAssets;
  }

  async getPipelineHash(pipeline) {
    let hash = _crypto().default.createHash('md5');

    for (let transformer of pipeline.transformers) {
      var _ref, _this$request$devDeps, _this$devDepRequests$;

      let key = `${transformer.name}:${transformer.resolveFrom}`;
      hash.update((_ref = (_this$request$devDeps = this.request.devDeps.get(key)) !== null && _this$request$devDeps !== void 0 ? _this$request$devDeps : (_this$devDepRequests$ = this.devDepRequests.get(key)) === null || _this$devDepRequests$ === void 0 ? void 0 : _this$devDepRequests$.hash) !== null && _ref !== void 0 ? _ref : '');
      let config = this.configs.get(transformer.name);

      if (config) {
        hash.update(config.id); // If there is no result hash set by the transformer, default to hashing the included
        // files if any, otherwise try to hash the config result itself.

        if (config.resultHash == null) {
          if (config.includedFiles.size > 0) {
            hash.update(await (0, _assetUtils().getInvalidationHash)([...config.includedFiles].map(filePath => ({
              type: 'file',
              filePath
            })), this.options));
          } else if (config.result != null) {
            try {
              // $FlowFixMe
              hash.update(_v().default.serialize(config.result));
            } catch (err) {
              throw new (_diagnostic().default)({
                diagnostic: {
                  message: 'Config result is not hashable because it contains non-serializable objects. Please use config.setResultHash to set the hash manually.',
                  origin: transformer.name
                }
              });
            }
          }
        } else {
          var _config$resultHash;

          hash.update((_config$resultHash = config.resultHash) !== null && _config$resultHash !== void 0 ? _config$resultHash : '');
        }

        for (let devDep of config.devDeps) {
          let key = `${devDep.moduleSpecifier}:${devDep.resolveFrom}`;
          hash.update((0, _nullthrows().default)(this.devDepRequests.get(key)).hash);
        }
      }
    }

    return hash.digest('hex');
  }

  async addDevDependency(opts, transformer) {
    let {
      moduleSpecifier,
      resolveFrom,
      invalidateParcelPlugin
    } = opts;
    let key = `${moduleSpecifier}:${resolveFrom}`;

    if (this.devDepRequests.has(key)) {
      return;
    } // If the request sent us a hash, we know the dev dep and all of its dependencies didn't change.
    // Reuse the same hash in the response. No need to send back invalidations as the request won't
    // be re-run anyway.


    let hash = this.request.devDeps.get(key);

    if (hash != null) {
      this.devDepRequests.set(key, {
        moduleSpecifier,
        resolveFrom,
        hash
      });
      return;
    } // Ensure that the package manager has an entry for this resolution.


    await this.options.packageManager.resolve(moduleSpecifier, resolveFrom);
    let invalidations = this.options.packageManager.getInvalidations(moduleSpecifier, resolveFrom); // It is possible for a transformer to have multiple different hashes due to
    // different dependencies (e.g. conditional requires) so we must always
    // recompute the hash and compare rather than only sending a transformer
    // dev dependency once.

    hash = await (0, _assetUtils().getInvalidationHash)([...invalidations.invalidateOnFileChange].map(f => ({
      type: 'file',
      filePath: f
    })), this.options);
    let devDepRequest = {
      moduleSpecifier,
      resolveFrom,
      hash,
      invalidateOnFileCreate: invalidations.invalidateOnFileCreate,
      invalidateOnFileChange: invalidations.invalidateOnFileChange
    }; // Optionally also invalidate the parcel plugin that is loading the config
    // when this dev dep changes (e.g. to invalidate local caches).

    if (invalidateParcelPlugin) {
      devDepRequest.additionalInvalidations = [{
        moduleSpecifier: transformer.name,
        resolveFrom: transformer.resolveFrom
      }];
    }

    this.devDepRequests.set(key, devDepRequest);
  }

  async runPipeline(pipeline, initialAsset) {
    if (pipeline.transformers.length === 0) {
      return [initialAsset];
    }

    let initialType = initialAsset.value.type;
    let inputAssets = [initialAsset];
    let resultingAssets = [];
    let finalAssets = [];

    for (let transformer of pipeline.transformers) {
      resultingAssets = [];

      for (let asset of inputAssets) {
        if (asset.value.type !== initialType && (await this.loadNextPipeline({
          filePath: initialAsset.value.filePath,
          isSource: asset.value.isSource,
          newType: asset.value.type,
          newPipeline: asset.value.pipeline,
          currentPipeline: pipeline
        }))) {
          finalAssets.push(asset);
          continue;
        }

        try {
          let transformerResults = await this.runTransformer(pipeline, asset, transformer.plugin, transformer.name, transformer.config);

          for (let result of transformerResults) {
            resultingAssets.push(asset.createChildAsset(result, transformer.name, this.parcelConfig.filePath, transformer.configKeyPath));
          }
        } catch (e) {
          throw new (_diagnostic().default)({
            diagnostic: (0, _diagnostic().errorToDiagnostic)(e, {
              origin: transformer.name,
              filePath: asset.value.filePath
            })
          });
        }
      }

      inputAssets = resultingAssets;
    } // Make assets with ASTs generate unless they are js assets and target uses
    // scope hoisting or we do CSS modules tree shaking. This parallelizes generation
    // and distributes work more evenly across workers than if one worker needed to
    // generate all assets in a large bundle during packaging.


    let generate = pipeline.generate;

    if (generate != null) {
      await Promise.all(resultingAssets.filter(asset => asset.ast != null && !(asset.value.env.shouldScopeHoist && asset.value.type === 'js' || this.options.mode === 'production' && asset.value.type === 'css' && asset.value.symbols)).map(async asset => {
        if (asset.isASTDirty) {
          var _output$map;

          let output = await generate(asset);
          asset.content = output.content;
          asset.mapBuffer = (_output$map = output.map) === null || _output$map === void 0 ? void 0 : _output$map.toBuffer();
        }

        asset.clearAST();
      }));
    }

    return finalAssets.concat(resultingAssets);
  }

  async readFromCache(cacheKey) {
    if (this.options.shouldDisableCache || this.request.code != null || this.request.invalidateReason & _constants().FILE_CREATE) {
      return null;
    }

    let cachedAssets = await this.options.cache.get(cacheKey);

    if (!cachedAssets) {
      return null;
    }

    return Promise.all(cachedAssets.map(async value => {
      let content = value.contentKey != null ? this.options.cache.getStream(value.contentKey) : null;
      let mapBuffer = value.astKey != null ? await this.options.cache.getBlob(value.astKey) : null;
      let ast = value.astKey != null ? // TODO: Capture with a test and likely use cache.get() as this returns a buffer.
      // $FlowFixMe[incompatible-call]
      await this.options.cache.getBlob(value.astKey) : null;
      return new (_UncommittedAsset().default)({
        value,
        options: this.options,
        content,
        mapBuffer,
        ast
      });
    }));
  }

  async writeToCache(cacheKey, assets, pipelineHash) {
    await Promise.all(assets.map(asset => asset.commit(pipelineHash)));
    this.options.cache.set(cacheKey, assets.map(a => a.value));
  }

  getCacheKey(assets, invalidationHash, pipelineHash) {
    let assetsKeyInfo = assets.map(a => ({
      filePath: a.value.filePath,
      pipeline: a.value.pipeline,
      hash: a.value.hash,
      uniqueKey: a.value.uniqueKey,
      query: a.value.query ? (0, _utils().objectSortedEntries)(a.value.query) : ''
    }));
    return (0, _utils().md5FromOrderedObject)({
      parcelVersion: _constants().PARCEL_VERSION,
      assets: assetsKeyInfo,
      env: this.request.env,
      invalidationHash,
      pipelineHash
    });
  }

  async loadPipeline(filePath, isSource, pipeline) {
    let transformers = await this.parcelConfig.getTransformers(filePath, pipeline, this.request.isURL);

    for (let transformer of transformers) {
      let config = await this.loadTransformerConfig(filePath, transformer, isSource);

      if (config) {
        this.configs.set(transformer.name, config);
      }
    }

    return {
      id: transformers.map(t => t.name).join(':'),
      transformers: transformers.map(transformer => {
        var _this$configs$get;

        return {
          name: transformer.name,
          resolveFrom: transformer.resolveFrom,
          config: (_this$configs$get = this.configs.get(transformer.name)) === null || _this$configs$get === void 0 ? void 0 : _this$configs$get.result,
          configKeyPath: transformer.keyPath,
          plugin: transformer.plugin
        };
      }),
      options: this.options,
      resolverRunner: new (_PathRequest().ResolverRunner)({
        config: this.parcelConfig,
        options: this.options
      }),
      pluginOptions: this.pluginOptions,
      workerApi: this.workerApi
    };
  }

  async loadNextPipeline({
    filePath,
    isSource,
    newType,
    newPipeline,
    currentPipeline
  }) {
    let nextFilePath = filePath.slice(0, -_path().default.extname(filePath).length) + '.' + newType;
    let nextPipeline = await this.loadPipeline(nextFilePath, isSource, newPipeline);

    if (nextPipeline.id === currentPipeline.id) {
      return null;
    }

    return nextPipeline;
  }

  async loadTransformerConfig(filePath, transformer, isSource) {
    let loadConfig = transformer.plugin.loadConfig;

    if (!loadConfig) {
      return;
    }

    let config = (0, _InternalConfig().createConfig)({
      plugin: transformer.name,
      isSource,
      searchPath: filePath,
      env: this.request.env
    });

    try {
      await loadConfig({
        config: new (_Config().default)(config, this.options),
        options: this.options,
        logger: new (_logger().PluginLogger)({
          origin: transformer.name
        })
      });
    } catch (e) {
      throw new (_diagnostic().default)({
        diagnostic: (0, _diagnostic().errorToDiagnostic)(e, {
          origin: transformer.name
        })
      });
    }

    for (let devDep of config.devDeps) {
      await this.addDevDependency(devDep, transformer);
    }

    return config;
  }

  async runTransformer(pipeline, asset, transformer, transformerName, preloadedConfig) {
    var _transformer$parse;

    const logger = new (_logger().PluginLogger)({
      origin: transformerName
    });

    const resolve = async (from, to) => {
      let result = (0, _nullthrows().default)(await pipeline.resolverRunner.resolve((0, _Dependency().createDependency)({
        env: asset.value.env,
        moduleSpecifier: to,
        sourcePath: from
      })));

      if (result.invalidateOnFileCreate) {
        this.invalidateOnFileCreate.push(...result.invalidateOnFileCreate);
      }

      if (result.invalidateOnFileChange) {
        for (let filePath of result.invalidateOnFileChange) {
          let invalidation = {
            type: 'file',
            filePath
          };
          this.invalidations.set((0, _assetUtils().getInvalidationId)(invalidation), invalidation);
        }
      }

      return result.assetGroup.filePath;
    }; // If an ast exists on the asset, but we cannot reuse it,
    // use the previous transform to generate code that we can re-parse.


    if (asset.ast && asset.isASTDirty && (!transformer.canReuseAST || !transformer.canReuseAST({
      ast: asset.ast,
      options: pipeline.pluginOptions,
      logger
    })) && pipeline.generate) {
      var _output$map2;

      let output = await pipeline.generate(asset);
      asset.content = output.content;
      asset.mapBuffer = (_output$map2 = output.map) === null || _output$map2 === void 0 ? void 0 : _output$map2.toBuffer();
    } // Load config for the transformer.


    let config = preloadedConfig; // Parse if there is no AST available from a previous transform.

    let parse = (_transformer$parse = transformer.parse) === null || _transformer$parse === void 0 ? void 0 : _transformer$parse.bind(transformer);

    if (!asset.ast && parse) {
      let ast = await parse({
        asset: new (_Asset().MutableAsset)(asset),
        config,
        options: pipeline.pluginOptions,
        resolve,
        logger
      });

      if (ast) {
        asset.setAST(ast);
        asset.isASTDirty = false;
      }
    } // Transform.


    let results = await normalizeAssets( // $FlowFixMe
    await transformer.transform({
      asset: new (_Asset().MutableAsset)(asset),
      ast: asset.ast,
      config,
      options: pipeline.pluginOptions,
      resolve,
      logger
    })); // Create generate function that can be called later

    pipeline.generate = input => {
      let ast = input.ast;
      let asset = new (_Asset().Asset)(input);

      if (transformer.generate && ast) {
        let generated = transformer.generate({
          asset,
          ast,
          options: pipeline.pluginOptions,
          logger
        });
        input.clearAST();
        return Promise.resolve(generated);
      }

      throw new Error('Asset has an AST but no generate method is available on the transform');
    };

    return results;
  }

}

exports.default = Transformation;

function normalizeAssets(results) {
  return Promise.all(results.map(async result => {
    if (!(result instanceof _Asset().MutableAsset)) {
      return result;
    }

    let internalAsset = (0, _Asset().mutableAssetToUncommittedAsset)(result); // $FlowFixMe - ignore id already on env

    return {
      ast: internalAsset.ast,
      content: await internalAsset.content,
      query: internalAsset.value.query,
      // $FlowFixMe
      dependencies: [...internalAsset.value.dependencies.values()],
      env: internalAsset.value.env,
      filePath: result.filePath,
      isInline: result.isInline,
      isIsolated: result.isIsolated,
      map: await internalAsset.getMap(),
      meta: result.meta,
      pipeline: internalAsset.value.pipeline,
      // $FlowFixMe
      symbols: internalAsset.value.symbols,
      type: result.type,
      uniqueKey: internalAsset.value.uniqueKey
    };
  }));
}