text: // @flow
  
  import {Transformer} from '@parcel/plugin';
  import {createDependencyLocation, isGlob, glob, globSync} from '@parcel/utils';
  import path from 'path';
  import nativeFS from 'fs';
  import stylus from 'stylus';
  import Parser from 'stylus/lib/parser';
  import DepsResolver from 'stylus/lib/visitor/deps-resolver';
  import nodes from 'stylus/lib/nodes';
  import utils from 'stylus/lib/utils';
  import Evaluator from 'stylus/lib/visitor/evaluator';
  
  const URL_RE = /^(?:url\s*\(\s*)?['"]?(?:[#/]|(?:https?:)?\/\/)/i;
  
  export default (new Transformer({
    async loadConfig({config}) {
      let configFile = await config.getConfig(['.stylusrc', '.stylusrc.js'], {
        packageKey: 'stylus',
      });
  
      if (configFile) {
        let isJavascript = path.extname(configFile.filePath) === '.js';
        if (isJavascript) {
          config.invalidateOnStartup();
        }
  
        // Resolve relative paths from config file
        if (configFile.contents.paths) {
          configFile.contents.paths = configFile.contents.paths.map(p =>
            path.resolve(path.dirname(configFile.filePath), p),
          );
        }
  
        return configFile.contents;
      }
    },
  
    async transform({asset, resolve, config, options}) {
      let stylusConfig = config ?? {};
      let code = await asset.getCode();
      let style = stylus(code, {...stylusConfig});
      style.set('filename', asset.filePath);
      style.set('include css', true);
      // Setup a handler for the URL function so we add dependencies for linked assets.
      style.define('url', (node: stylus.nodes.String | stylus.nodes.Literal) => {
        let filename = asset.addURLDependency(node.val, {
          loc: createDependencyLocation(
            {line: node.lineno, column: node.column},
            node.val,
          ),
        });
        return new stylus.nodes.Literal(`url(${JSON.stringify(filename)})`);
      });
  
      let {resolved: stylusPath} = await options.packageManager.resolve(
        'stylus',
        __filename,
      );
      let nativeGlob = await options.packageManager.require('glob', stylusPath);
  
      style.set(
        'Evaluator',
        await createEvaluator(
          code,
          asset,
          resolve,
          style.options,
          options,
          nativeGlob,
        ),
      );
  
      asset.type = 'css';
      asset.setCode(style.render());
      asset.meta.hasDependencies = false;
      return [asset];
    },
  }): Transformer);
  
  function attemptResolve(importedPath, filepath, asset, resolve, deps) {
    if (deps.has(importedPath)) {
      return;
    }
  
    if (isGlob(importedPath)) {
      // Invalidate when new files are created that match the glob pattern.
      let absoluteGlob = path.resolve(path.dirname(filepath), importedPath);
      asset.invalidateOnFileCreate({glob: absoluteGlob});
  
      deps.set(
        importedPath,
        glob(absoluteGlob, asset.fs, {
          onlyFiles: true,
        }).then(entries =>
          Promise.all(
            entries.map(entry =>
              resolve(
                filepath,
                './' + path.relative(path.dirname(filepath), entry),
              ),
            ),
          ),
        ),
      );
    } else {
      deps.set(importedPath, resolve(filepath, importedPath));
    }
  }
  
  async function getDependencies(
    code,
    filepath,
    asset,
    resolve,
    options,
    parcelOptions,
    nativeGlob,
    seen = new Set(),
    includeImports = true,
  ) {
    seen.add(filepath);
  
    nodes.filename = asset.filePath;
  
    let parser = new Parser(code, options);
    let ast = parser.parse();
    let deps = new Map();
  
    if (includeImports && options.imports) {
      for (let importedPath of options.imports) {
        attemptResolve(importedPath, filepath, asset, resolve, deps);
      }
    }
  
    class ImportVisitor extends DepsResolver {
      visitImport(imported) {
        let importedPath = imported.path.first.string;
        attemptResolve(importedPath, filepath, asset, resolve, deps);
      }
    }
  
    new ImportVisitor(ast, options).visit(ast);
  
    // Recursively process depdendencies, and return a map with all resolved paths.
    let res = new Map();
    await Promise.all(
      Array.from(deps.entries()).map(async ([importedPath, resolved]) => {
        try {
          resolved = await resolved;
        } catch (err) {
          resolved = null;
        }
  
        let found;
        if (resolved && (!Array.isArray(resolved) || resolved.length > 0)) {
          found = Array.isArray(resolved) ? resolved : [resolved];
          res.set(importedPath, resolved);
        } else {
          // If we couldn't resolve, try the normal stylus resolver.
          // We just need to do this to keep track of the dependencies - stylus does the real work.
  
          // support optional .styl
          let originalPath = importedPath;
          if (!/\.styl$/i.test(importedPath)) {
            importedPath += '.styl';
          }
  
          // Patch the native FS so we use Parcel's FS, and track files that are
          // checked so we invalidate the cache when they are created.
          let restore = patchNativeFS(asset.fs, nativeGlob);
  
          let paths = [
            ...new Set(
              (options.paths || []).concat(path.dirname(filepath || '.')),
            ),
          ];
          found = utils.find(importedPath, paths, filepath);
          if (!found) {
            found = utils.lookupIndex(originalPath, paths, filepath);
          }
  
          for (let invalidation of restore()) {
            asset.invalidateOnFileCreate(invalidation);
          }
  
          if (!found) {
            throw new Error('failed to locate file ' + originalPath);
          }
        }
  
        // Recursively process resolved files as well to get nested deps
        for (let resolved of found) {
          if (!seen.has(resolved)) {
            await asset.invalidateOnFileChange(resolved);
  
            let code = await asset.fs.readFile(resolved, 'utf8');
            for (let [path, resolvedPath] of await getDependencies(
              code,
              resolved,
              asset,
              resolve,
              options,
              parcelOptions,
              nativeGlob,
              seen,
              false,
            )) {
              res.set(path, resolvedPath);
            }
          }
        }
      }),
    );
  
    return res;
  }
  
  async function createEvaluator(
    code,
    asset,
    resolve,
    options,
    parcelOptions,
    nativeGlob,
  ) {
    const deps = await getDependencies(
      code,
      asset.filePath,
      asset,
      resolve,
      options,
      parcelOptions,
      nativeGlob,
    );
  
    // This is a custom stylus evaluator that extends stylus with support for the node
    // require resolution algorithm. It also adds all dependencies to the parcel asset
    // tree so the file watcher works correctly, etc.
    class CustomEvaluator extends Evaluator {
      visitImport(imported) {
        let node = this.visit(imported.path).first;
        let path = node.string;
        if (node.name !== 'url' && path && !URL_RE.test(path)) {
          let resolved = deps.get(path);
  
          // First try resolving using the node require resolution algorithm.
          // This allows stylus files in node_modules to be resolved properly.
          // If we find something, update the AST so stylus gets the absolute path to load later.
          if (resolved) {
            if (!Array.isArray(resolved)) {
              node.string = resolved;
            } else {
              // If the import resolves to multiple files (i.e. glob),
              // replace it with a separate import node for each file
              return mergeBlocks(
                resolved.map(resolvedPath => {
                  node.string = resolvedPath;
                  let restore = patchNativeFS(asset.fs, nativeGlob);
                  let res = super.visitImport(imported.clone());
                  restore();
                  return res;
                }),
              );
            }
          }
        }
  
        // Patch the native FS so stylus uses Parcel's FS to read the file.
        let restore = patchNativeFS(asset.fs, nativeGlob);
  
        // Done. Let stylus do its thing.
        let res = super.visitImport(imported);
  
        restore();
        return res;
      }
    }
  
    return CustomEvaluator;
  }
  
  function patchNativeFS(fs, nativeGlob) {
    let invalidations = [];
    let readFileSync = nativeFS.readFileSync;
    let statSync = nativeFS.statSync;
  
    // $FlowFixMe
    nativeFS.readFileSync = (filename, encoding) => {
      return fs.readFileSync(filename, encoding);
    };
  
    // $FlowFixMe
    nativeFS.statSync = p => {
      try {
        return fs.statSync(p);
      } catch (err) {
        // Track files that were checked but don't exist so that we watch for their creation.
        if (!p.includes(`node_modules${path.sep}stylus`)) {
          invalidations.push({filePath: p});
        }
        throw err;
      }
    };
  
    // Patch the `glob` module as well so we use the Parcel FS and track invalidations.
    let glob = nativeGlob.sync;
    nativeGlob.sync = p => {
      let res = globSync(p, fs);
      if (!p.includes(`node_modules${path.sep}stylus`)) {
        // Sometimes stylus passes file paths with no glob parts to the `glob` module.
        // We want to avoid treating these as globs for performance.
        if (isGlob(p)) {
          invalidations.push({glob: p});
        } else if (res.length === 0) {
          invalidations.push({filePath: p});
        }
      }
      return res;
    };
  
    return () => {
      // $FlowFixMe
      nativeFS.readFileSync = readFileSync;
      // $FlowFixMe
      nativeFS.statSync = statSync;
      nativeGlob.sync = glob;
      return invalidations;
    };
  }
  
  /**
   * Puts the content of all given node blocks into the first one, essentially merging them.
   */
  function mergeBlocks(blocks) {
    let finalBlock;
    for (const block of blocks) {
      if (finalBlock) {
        // $FlowFixMe - finalBlock is definitely defined
        block.nodes.forEach(node => finalBlock.push(node));
      } else {
        finalBlock = block;
      }
    }
    return finalBlock;
  }
  