code:
'use strict';

const fs = require('fs');
const path = require('path');
const colors = require('picocolors');
const { loadConfig, optimize } = require('../svgo-node.js');
const pluginsMap = require('../../plugins/plugins.js');
const PKG = require('../../package.json');
const { encodeSVGDatauri, decodeSVGDatauri } = require('./tools.js');

const regSVGFile = /\.svg$/i;

/**
 * Synchronously check if path is a directory. Tolerant to errors like ENOENT.
 * @param {string} path
 */
function checkIsDir(path) {
  try {
    return fs.lstatSync(path).isDirectory();
  } catch (e) {
    return false;
  }
}

module.exports = function makeProgram(program) {
  program
    .name(PKG.name)
    .description(PKG.description, {
      INPUT: 'Alias to --input',
    })
    .version(PKG.version, '-v, --version')
    .arguments('[INPUT...]')
    .option('-i, --input <INPUT...>', 'Input files, "-" for STDIN')
    .option('-s, --string <STRING>', 'Input SVG data string')
    .option(
      '-f, --folder <FOLDER>',
      'Input folder, optimize and rewrite all *.svg files'
    )
    .option(
      '-o, --output <OUTPUT...>',
      'Output file or folder (by default the same as the input), "-" for STDOUT'
    )
    .option(
      '-p, --precision <INTEGER>',
      'Set number of digits in the fractional part, overrides plugins params'
    )
    .option('--config <CONFIG>', 'Custom config file, only .js is supported')
    .option(
      '--datauri <FORMAT>',
      'Output as Data URI string (base64), URI encoded (enc) or unencoded (unenc)'
    )
    .option(
      '--multipass',
      'Pass over SVGs multiple times to ensure all optimizations are applied'
    )
    .option('--pretty', 'Make SVG pretty printed')
    .option('--indent <INTEGER>', 'Indent number when pretty printing SVGs')
    .option(
      '--eol <EOL>',
      'Line break to use when outputting SVG: lf, crlf. If unspecified, uses platform default.'
    )
    .option('--final-newline', 'Ensure SVG ends with a line break')
    .option(
      '-r, --recursive',
      "Use with '--folder'. Optimizes *.svg files in folders recursively."
    )
    .option(
      '--exclude <PATTERN...>',
      "Use with '--folder'. Exclude files matching regular expression pattern."
    )
    .option(
      '-q, --quiet',
      'Only output error messages, not regular status messages'
    )
    .option('--show-plugins', 'Show available plugins and exit')
    // used by picocolors internally
    .option('--no-color', 'Output plain text without color')
    .action(action);
};

async function action(args, opts, command) {
  var input = opts.input || args;
  var output = opts.output;
  var config = {};

  if (opts.precision != null) {
    const number = Number.parseInt(opts.precision, 10);
    if (Number.isNaN(number)) {
      console.error(
        "error: option '-p, --precision' argument must be an integer number"
      );
      process.exit(1);
    } else {
      opts.precision = number;
    }
  }

  if (opts.datauri != null) {
    if (
      opts.datauri !== 'base64' &&
      opts.datauri !== 'enc' &&
      opts.datauri !== 'unenc'
    ) {
      console.error(
        "error: option '--datauri' must have one of the following values: 'base64', 'enc' or 'unenc'"
      );
      process.exit(1);
    }
  }

  if (opts.indent != null) {
    const number = Number.parseInt(opts.indent, 10);
    if (Number.isNaN(number)) {
      console.error(
        "error: option '--indent' argument must be an integer number"
      );
      process.exit(1);
    } else {
      opts.indent = number;
    }
  }

  if (opts.eol != null && opts.eol !== 'lf' && opts.eol !== 'crlf') {
    console.error(
      "error: option '--eol' must have one of the following values: 'lf' or 'crlf'"
    );
    process.exit(1);
  }

  // --show-plugins
  if (opts.showPlugins) {
    showAvailablePlugins();
    return;
  }

  // w/o anything
  if (
    (input.length === 0 || input[0] === '-') &&
    !opts.string &&
    !opts.stdin &&
    !opts.folder &&
    process.stdin.isTTY === true
  ) {
    return command.help();
  }

  if (
    typeof process == 'object' &&
    process.versions &&
    process.versions.node &&
    PKG &&
    PKG.engines.node
  ) {
    var nodeVersion = String(PKG.engines.node).match(/\d*(\.\d+)*/)[0];
    if (parseFloat(process.versions.node) < parseFloat(nodeVersion)) {
      throw Error(
        `${PKG.name} requires Node.js version ${nodeVersion} or higher.`
      );
    }
  }

  // --config
  const loadedConfig = await loadConfig(opts.config);
  if (loadedConfig != null) {
    config = loadedConfig;
  }

  // --quiet
  if (opts.quiet) {
    config.quiet = opts.quiet;
  }

  // --recursive
  if (opts.recursive) {
    config.recursive = opts.recursive;
  }

  // --exclude
  config.exclude = opts.exclude
    ? opts.exclude.map((pattern) => RegExp(pattern))
    : [];

  // --precision
  if (opts.precision != null) {
    var precision = Math.min(Math.max(0, opts.precision), 20);
    config.floatPrecision = precision;
  }

  // --multipass
  if (opts.multipass) {
    config.multipass = true;
  }

  // --pretty
  if (opts.pretty) {
    config.js2svg = config.js2svg || {};
    config.js2svg.pretty = true;
    if (opts.indent != null) {
      config.js2svg.indent = opts.indent;
    }
  }

  // --eol
  if (opts.eol) {
    config.js2svg = config.js2svg || {};
    config.js2svg.eol = opts.eol;
  }

  // --final-newline
  if (opts.finalNewline) {
    config.js2svg = config.js2svg || {};
    config.js2svg.finalNewline = true;
  }

  // --output
  if (output) {
    if (input.length && input[0] != '-') {
      if (output.length == 1 && checkIsDir(output[0])) {
        var dir = output[0];
        for (var i = 0; i < input.length; i++) {
          output[i] = checkIsDir(input[i])
            ? input[i]
            : path.resolve(dir, path.basename(input[i]));
        }
      } else if (output.length < input.length) {
        output = output.concat(input.slice(output.length));
      }
    }
  } else if (input.length) {
    output = input;
  } else if (opts.string) {
    output = '-';
  }

  if (opts.datauri) {
    config.datauri = opts.datauri;
  }

  // --folder
  if (opts.folder) {
    var ouputFolder = (output && output[0]) || opts.folder;
    await optimizeFolder(config, opts.folder, ouputFolder);
  }

  // --input
  if (input.length !== 0) {
    // STDIN
    if (input[0] === '-') {
      return new Promise((resolve, reject) => {
        var data = '',
          file = output[0];

        process.stdin
          .on('data', (chunk) => (data += chunk))
          .once('end', () =>
            processSVGData(config, { input: 'string' }, data, file).then(
              resolve,
              reject
            )
          );
      });
      // file
    } else {
      await Promise.all(
        input.map((file, n) => optimizeFile(config, file, output[n]))
      );
    }

    // --string
  } else if (opts.string) {
    var data = decodeSVGDatauri(opts.string);

    return processSVGData(config, { input: 'string' }, data, output[0]);
  }
}

/**
 * Optimize SVG files in a directory.
 * @param {Object} config options
 * @param {string} dir input directory
 * @param {string} output output directory
 * @return {Promise}
 */
function optimizeFolder(config, dir, output) {
  if (!config.quiet) {
    console.log(`Processing directory '${dir}':\n`);
  }
  return fs.promises
    .readdir(dir)
    .then((files) => processDirectory(config, dir, files, output));
}

/**
 * Process given files, take only SVG.
 * @param {Object} config options
 * @param {string} dir input directory
 * @param {Array} files list of file names in the directory
 * @param {string} output output directory
 * @return {Promise}
 */
function processDirectory(config, dir, files, output) {
  // take only *.svg files, recursively if necessary
  var svgFilesDescriptions = getFilesDescriptions(config, dir, files, output);

  return svgFilesDescriptions.length
    ? Promise.all(
        svgFilesDescriptions.map((fileDescription) =>
          optimizeFile(
            config,
            fileDescription.inputPath,
            fileDescription.outputPath
          )
        )
      )
    : Promise.reject(
        new Error(`No SVG files have been found in '${dir}' directory.`)
      );
}

/**
 * Get svg files descriptions
 * @param {Object} config options
 * @param {string} dir input directory
 * @param {Array} files list of file names in the directory
 * @param {string} output output directory
 * @return {Array}
 */
function getFilesDescriptions(config, dir, files, output) {
  const filesInThisFolder = files
    .filter(
      (name) =>
        regSVGFile.test(name) &&
        !config.exclude.some((regExclude) => regExclude.test(name))
    )
    .map((name) => ({
      inputPath: path.resolve(dir, name),
      outputPath: path.resolve(output, name),
    }));

  return config.recursive
    ? [].concat(
        filesInThisFolder,
        files
          .filter((name) => checkIsDir(path.resolve(dir, name)))
          .map((subFolderName) => {
            const subFolderPath = path.resolve(dir, subFolderName);
            const subFolderFiles = fs.readdirSync(subFolderPath);
            const subFolderOutput = path.resolve(output, subFolderName);
            return getFilesDescriptions(
              config,
              subFolderPath,
              subFolderFiles,
              subFolderOutput
            );
          })
          .reduce((a, b) => [].concat(a, b), [])
      )
    : filesInThisFolder;
}

/**
 * Read SVG file and pass to processing.
 * @param {Object} config options
 * @param {string} file
 * @param {string} output
 * @return {Promise}
 */
function optimizeFile(config, file, output) {
  return fs.promises.readFile(file, 'utf8').then(
    (data) =>
      processSVGData(config, { input: 'file', path: file }, data, output, file),
    (error) => checkOptimizeFileError(config, file, output, error)
  );
}

/**
 * Optimize SVG data.
 * @param {Object} config options
 * @param {string} data SVG content to optimize
 * @param {string} output where to write optimized file
 * @param {string} [input] input file name (being used if output is a directory)
 * @return {Promise}
 */
function processSVGData(config, info, data, output, input) {
  var startTime = Date.now(),
    prevFileSize = Buffer.byteLength(data, 'utf8');

  const result = optimize(data, { ...config, ...info });
  if (result.modernError) {
    console.error(colors.red(result.modernError.toString()));
    process.exit(1);
  }
  if (config.datauri) {
    result.data = encodeSVGDatauri(result.data, config.datauri);
  }
  var resultFileSize = Buffer.byteLength(result.data, 'utf8'),
    processingTime = Date.now() - startTime;

  return writeOutput(input, output, result.data).then(
    function () {
      if (!config.quiet && output != '-') {
        if (input) {
          console.log(`\n${path.basename(input)}:`);
        }
        printTimeInfo(processingTime);
        printProfitInfo(prevFileSize, resultFileSize);
      }
    },
    (error) =>
      Promise.reject(
        new Error(
          error.code === 'ENOTDIR'
            ? `Error: output '${output}' is not a directory.`
            : error
        )
      )
  );
}

/**
 * Write result of an optimization.
 * @param {string} input
 * @param {string} output output file name. '-' for stdout
 * @param {string} data data to write
 * @return {Promise}
 */
function writeOutput(input, output, data) {
  if (output == '-') {
    console.log(data);
    return Promise.resolve();
  }

  fs.mkdirSync(path.dirname(output), { recursive: true });

  return fs.promises
    .writeFile(output, data, 'utf8')
    .catch((error) => checkWriteFileError(input, output, data, error));
}

/**
 * Write a time taken by optimization.
 * @param {number} time time in milliseconds.
 */
function printTimeInfo(time) {
  console.log(`Done in ${time} ms!`);
}

/**
 * Write optimizing information in human readable format.
 * @param {number} inBytes size before optimization.
 * @param {number} outBytes size after optimization.
 */
function printProfitInfo(inBytes, outBytes) {
  var profitPercents = 100 - (outBytes * 100) / inBytes;

  console.log(
    Math.round((inBytes / 1024) * 1000) / 1000 +
      ' KiB' +
      (profitPercents < 0 ? ' + ' : ' - ') +
      colors.green(Math.abs(Math.round(profitPercents * 10) / 10) + '%') +
      ' = ' +
      Math.round((outBytes / 1024) * 1000) / 1000 +
      ' KiB'
  );
}

/**
 * Check for errors, if it's a dir optimize the dir.
 * @param {Object} config
 * @param {string} input
 * @param {string} output
 * @param {Error} error
 * @return {Promise}
 */
function checkOptimizeFileError(config, input, output, error) {
  if (error.code == 'EISDIR') {
    return optimizeFolder(config, input, output);
  } else if (error.code == 'ENOENT') {
    return Promise.reject(
      new Error(`Error: no such file or directory '${error.path}'.`)
    );
  }
  return Promise.reject(error);
}

/**
 * Check for saving file error. If the output is a dir, then write file there.
 * @param {string} input
 * @param {string} output
 * @param {string} data
 * @param {Error} error
 * @return {Promise}
 */
function checkWriteFileError(input, output, data, error) {
  if (error.code == 'EISDIR' && input) {
    return fs.promises.writeFile(
      path.resolve(output, path.basename(input)),
      data,
      'utf8'
    );
  } else {
    return Promise.reject(error);
  }
}

/**
 * Show list of available plugins with short description.
 */
function showAvailablePlugins() {
  const list = Object.entries(pluginsMap)
    .sort(([a], [b]) => a.localeCompare(b))
    .map(([name, plugin]) => ` [ ${colors.green(name)} ] ${plugin.description}`)
    .join('\n');
  console.log('Currently available plugins:\n' + list);
}

module.exports.checkIsDir = checkIsDir;


code:
'use strict';

const pluginsMap = require('../../plugins/plugins.js');

const pluginsOrder = [
  'removeDoctype',
  'removeXMLProcInst',
  'removeComments',
  'removeMetadata',
  'removeXMLNS',
  'removeEditorsNSData',
  'cleanupAttrs',
  'mergeStyles',
  'inlineStyles',
  'minifyStyles',
  'convertStyleToAttrs',
  'cleanupIDs',
  'prefixIds',
  'removeRasterImages',
  'removeUselessDefs',
  'cleanupNumericValues',
  'cleanupListOfValues',
  'convertColors',
  'removeUnknownsAndDefaults',
  'removeNonInheritableGroupAttrs',
  'removeUselessStrokeAndFill',
  'removeViewBox',
  'cleanupEnableBackground',
  'removeHiddenElems',
  'removeEmptyText',
  'convertShapeToPath',
  'convertEllipseToCircle',
  'moveElemsAttrsToGroup',
  'moveGroupAttrsToElems',
  'collapseGroups',
  'convertPathData',
  'convertTransform',
  'removeEmptyAttrs',
  'removeEmptyContainers',
  'mergePaths',
  'removeUnusedNS',
  'sortAttrs',
  'sortDefsChildren',
  'removeTitle',
  'removeDesc',
  'removeDimensions',
  'removeAttrs',
  'removeAttributesBySelector',
  'removeElementsByAttr',
  'addClassesToSVGElement',
  'removeStyleElement',
  'removeScriptElement',
  'addAttributesToSVGElement',
  'removeOffCanvasPaths',
  'reusePaths',
];
const defaultPlugins = pluginsOrder.filter((name) => pluginsMap[name].active);
exports.defaultPlugins = defaultPlugins;

const extendDefaultPlugins = (plugins) => {
  console.warn(
    '\n"extendDefaultPlugins" utility is deprecated.\n' +
      'Use "preset-default" plugin with overrides instead.\n' +
      'For example:\n' +
      `{\n` +
      `  name: 'preset-default',\n` +
      `  params: {\n` +
      `    overrides: {\n` +
      `      // customize plugin options\n` +
      `      convertShapeToPath: {\n` +
      `        convertArcs: true\n` +
      `      },\n` +
      `      // disable plugins\n` +
      `      convertPathData: false\n` +
      `    }\n` +
      `  }\n` +
      `}\n`
  );
  const extendedPlugins = pluginsOrder.map((name) => ({
    name,
    active: pluginsMap[name].active,
  }));
  for (const plugin of plugins) {
    const resolvedPlugin = resolvePluginConfig(plugin);
    const index = pluginsOrder.indexOf(resolvedPlugin.name);
    if (index === -1) {
      extendedPlugins.push(plugin);
    } else {
      extendedPlugins[index] = plugin;
    }
  }
  return extendedPlugins;
};
exports.extendDefaultPlugins = extendDefaultPlugins;

const resolvePluginConfig = (plugin) => {
  let configParams = {};
  if (typeof plugin === 'string') {
    // resolve builtin plugin specified as string
    const pluginConfig = pluginsMap[plugin];
    if (pluginConfig == null) {
      throw Error(`Unknown builtin plugin "${plugin}" specified.`);
    }
    return {
      ...pluginConfig,
      name: plugin,
      active: true,
      params: { ...pluginConfig.params, ...configParams },
    };
  }
  if (typeof plugin === 'object' && plugin != null) {
    if (plugin.name == null) {
      throw Error(`Plugin name should be specified`);
    }
    if (plugin.fn) {
      // resolve custom plugin with implementation
      return {
        active: true,
        ...plugin,
        params: { ...configParams, ...plugin.params },
      };
    } else {
      // resolve builtin plugin specified as object without implementation
      const pluginConfig = pluginsMap[plugin.name];
      if (pluginConfig == null) {
        throw Error(`Unknown builtin plugin "${plugin.name}" specified.`);
      }
      return {
        ...pluginConfig,
        active: true,
        ...plugin,
        params: { ...pluginConfig.params, ...configParams, ...plugin.params },
      };
    }
  }
  return null;
};
exports.resolvePluginConfig = resolvePluginConfig;

license: mit


code:
'use strict';

var CSSClassList = function (node) {
  this.parentNode = node;
  this.classNames = new Set();
  const value = node.attributes.class;
  if (value != null) {
    this.addClassValueHandler();
    this.setClassValue(value);
  }
};

// attr.class.value

CSSClassList.prototype.addClassValueHandler = function () {
  Object.defineProperty(this.parentNode.attributes, 'class', {
    get: this.getClassValue.bind(this),
    set: this.setClassValue.bind(this),
    enumerable: true,
    configurable: true,
  });
};

CSSClassList.prototype.getClassValue = function () {
  var arrClassNames = Array.from(this.classNames);
  return arrClassNames.join(' ');
};

CSSClassList.prototype.setClassValue = function (newValue) {
  if (typeof newValue === 'undefined') {
    this.classNames.clear();
    return;
  }
  var arrClassNames = newValue.split(' ');
  this.classNames = new Set(arrClassNames);
};

CSSClassList.prototype.add = function (/* variadic */) {
  this.addClassValueHandler();
  Object.values(arguments).forEach(this._addSingle.bind(this));
};

CSSClassList.prototype._addSingle = function (className) {
  this.classNames.add(className);
};

CSSClassList.prototype.remove = function (/* variadic */) {
  this.addClassValueHandler();
  Object.values(arguments).forEach(this._removeSingle.bind(this));
};

CSSClassList.prototype._removeSingle = function (className) {
  this.classNames.delete(className);
};

CSSClassList.prototype.item = function (index) {
  var arrClassNames = Array.from(this.classNames);
  return arrClassNames[index];
};

CSSClassList.prototype.toggle = function (className, force) {
  if (this.contains(className) || force === false) {
    this.classNames.delete(className);
  }
  this.classNames.add(className);
};

CSSClassList.prototype.contains = function (className) {
  return this.classNames.has(className);
};

module.exports = CSSClassList;


code:
'use strict';

const isTag = (node) => {
  return node.type === 'element';
};

const existsOne = (test, elems) => {
  return elems.some((elem) => {
    if (isTag(elem)) {
      return test(elem) || existsOne(test, getChildren(elem));
    } else {
      return false;
    }
  });
};

const getAttributeValue = (elem, name) => {
  return elem.attributes[name];
};

const getChildren = (node) => {
  return node.children || [];
};

const getName = (elemAst) => {
  return elemAst.name;
};

const getParent = (node) => {
  return node.parentNode || null;
};

const getSiblings = (elem) => {
  var parent = getParent(elem);
  return parent ? getChildren(parent) : [];
};

const getText = (node) => {
  if (node.children[0].type === 'text' && node.children[0].type === 'cdata') {
    return node.children[0].value;
  }
  return '';
};

const hasAttrib = (elem, name) => {
  return elem.attributes[name] !== undefined;
};

const removeSubsets = (nodes) => {
  let idx = nodes.length;
  let node;
  let ancestor;
  let replace;
  // Check if each node (or one of its ancestors) is already contained in the
  // array.
  while (--idx > -1) {
    node = ancestor = nodes[idx];
    // Temporarily remove the node under consideration
    nodes[idx] = null;
    replace = true;
    while (ancestor) {
      if (nodes.includes(ancestor)) {
        replace = false;
        nodes.splice(idx, 1);
        break;
      }
      ancestor = getParent(ancestor);
    }
    // If the node has been found to be unique, re-insert it.
    if (replace) {
      nodes[idx] = node;
    }
  }
  return nodes;
};

const findAll = (test, elems) => {
  const result = [];
  for (const elem of elems) {
    if (isTag(elem)) {
      if (test(elem)) {
        result.push(elem);
      }
      result.push(...findAll(test, getChildren(elem)));
    }
  }
  return result;
};

const findOne = (test, elems) => {
  for (const elem of elems) {
    if (isTag(elem)) {
      if (test(elem)) {
        return elem;
      }
      const result = findOne(test, getChildren(elem));
      if (result) {
        return result;
      }
    }
  }
  return null;
};

const svgoCssSelectAdapter = {
  isTag,
  existsOne,
  getAttributeValue,
  getChildren,
  getName,
  getParent,
  getSiblings,
  getText,
  hasAttrib,
  removeSubsets,
  findAll,
  findOne,
};

module.exports = svgoCssSelectAdapter;


code:
'use strict';

var csstree = require('css-tree'),
  csstools = require('../css-tools');

var CSSStyleDeclaration = function (node) {
  this.parentNode = node;

  this.properties = new Map();
  this.hasSynced = false;

  this.styleValue = null;

  this.parseError = false;
  const value = node.attributes.style;
  if (value != null) {
    this.addStyleValueHandler();
    this.setStyleValue(value);
  }
};

// attr.style.value

CSSStyleDeclaration.prototype.addStyleValueHandler = function () {
  Object.defineProperty(this.parentNode.attributes, 'style', {
    get: this.getStyleValue.bind(this),
    set: this.setStyleValue.bind(this),
    enumerable: true,
    configurable: true,
  });
};

CSSStyleDeclaration.prototype.getStyleValue = function () {
  return this.getCssText();
};

CSSStyleDeclaration.prototype.setStyleValue = function (newValue) {
  this.properties.clear(); // reset all existing properties
  this.styleValue = newValue;
  this.hasSynced = false; // raw css changed
};

CSSStyleDeclaration.prototype._loadCssText = function () {
  if (this.hasSynced) {
    return;
  }
  this.hasSynced = true; // must be set here to prevent loop in setProperty(...)

  if (!this.styleValue || this.styleValue.length === 0) {
    return;
  }
  var inlineCssStr = this.styleValue;

  var declarations = {};
  try {
    declarations = csstree.parse(inlineCssStr, {
      context: 'declarationList',
      parseValue: false,
    });
  } catch (parseError) {
    this.parseError = parseError;
    return;
  }
  this.parseError = false;

  var self = this;
  declarations.children.each(function (declaration) {
    try {
      var styleDeclaration = csstools.csstreeToStyleDeclaration(declaration);
      self.setProperty(
        styleDeclaration.name,
        styleDeclaration.value,
        styleDeclaration.priority
      );
    } catch (styleError) {
      if (styleError.message !== 'Unknown node type: undefined') {
        self.parseError = styleError;
      }
    }
  });
};

// only reads from properties

/**
 * Get the textual representation of the declaration block (equivalent to .cssText attribute).
 *
 * @return {string} Textual representation of the declaration block (empty string for no properties)
 */
CSSStyleDeclaration.prototype.getCssText = function () {
  var properties = this.getProperties();

  if (this.parseError) {
    // in case of a parse error, pass through original styles
    return this.styleValue;
  }

  var cssText = [];
  properties.forEach(function (property, propertyName) {
    var strImportant = property.priority === 'important' ? '!important' : '';
    cssText.push(
      propertyName.trim() + ':' + property.value.trim() + strImportant
    );
  });
  return cssText.join(';');
};

CSSStyleDeclaration.prototype._handleParseError = function () {
  if (this.parseError) {
    console.warn(
      "Warning: Parse error when parsing inline styles, style properties of this element cannot be used. The raw styles can still be get/set using .attr('style').value. Error details: " +
        this.parseError
    );
  }
};

CSSStyleDeclaration.prototype._getProperty = function (propertyName) {
  if (typeof propertyName === 'undefined') {
    throw Error('1 argument required, but only 0 present.');
  }

  var properties = this.getProperties();
  this._handleParseError();

  var property = properties.get(propertyName.trim());
  return property;
};

/**
 * Return the optional priority, "important".
 *
 * @param {string} propertyName representing the property name to be checked.
 * @return {string} priority that represents the priority (e.g. "important") if one exists. If none exists, returns the empty string.
 */
CSSStyleDeclaration.prototype.getPropertyPriority = function (propertyName) {
  var property = this._getProperty(propertyName);
  return property ? property.priority : '';
};

/**
 * Return the property value given a property name.
 *
 * @param {string} propertyName representing the property name to be checked.
 * @return {string} value containing the value of the property. If not set, returns the empty string.
 */
CSSStyleDeclaration.prototype.getPropertyValue = function (propertyName) {
  var property = this._getProperty(propertyName);
  return property ? property.value : null;
};

/**
 * Return a property name.
 *
 * @param {number} index of the node to be fetched. The index is zero-based.
 * @return {string} propertyName that is the name of the CSS property at the specified index.
 */
CSSStyleDeclaration.prototype.item = function (index) {
  if (typeof index === 'undefined') {
    throw Error('1 argument required, but only 0 present.');
  }

  var properties = this.getProperties();
  this._handleParseError();

  return Array.from(properties.keys())[index];
};

/**
 * Return all properties of the node.
 *
 * @return {Map} properties that is a Map with propertyName as key and property (propertyValue + propertyPriority) as value.
 */
CSSStyleDeclaration.prototype.getProperties = function () {
  this._loadCssText();
  return this.properties;
};

// writes to properties

/**
 * Remove a property from the CSS declaration block.
 *
 * @param {string} propertyName representing the property name to be removed.
 * @return {string} oldValue equal to the value of the CSS property before it was removed.
 */
CSSStyleDeclaration.prototype.removeProperty = function (propertyName) {
  if (typeof propertyName === 'undefined') {
    throw Error('1 argument required, but only 0 present.');
  }

  this.addStyleValueHandler();

  var properties = this.getProperties();
  this._handleParseError();

  var oldValue = this.getPropertyValue(propertyName);
  properties.delete(propertyName.trim());
  return oldValue;
};

/**
 * Modify an existing CSS property or creates a new CSS property in the declaration block.
 *
 * @param {string} propertyName representing the CSS property name to be modified.
 * @param {string} value containing the new property value. If not specified, treated as the empty string. value must not contain "!important" -- that should be set using the priority parameter.
 * @param {string} priority allowing the "important" CSS priority to be set. If not specified, treated as the empty string.
 * @return {{value: string, priority: string}}
 */
CSSStyleDeclaration.prototype.setProperty = function (
  propertyName,
  value,
  priority
) {
  if (typeof propertyName === 'undefined') {
    throw Error('propertyName argument required, but only not present.');
  }

  this.addStyleValueHandler();

  var properties = this.getProperties();
  this._handleParseError();

  var property = {
    value: value.trim(),
    priority: priority.trim(),
  };
  properties.set(propertyName.trim(), property);

  return property;
};

module.exports = CSSStyleDeclaration;


code:
'use strict';

const { selectAll, selectOne, is } = require('css-select');
const svgoCssSelectAdapter = require('./css-select-adapter');
const CSSClassList = require('./css-class-list');
const CSSStyleDeclaration = require('./css-style-declaration');

/**
 * @type {(name: string) => { prefix: string, local: string }}
 */
const parseName = (name) => {
  if (name == null) {
    return {
      prefix: '',
      local: '',
    };
  }
  if (name === 'xmlns') {
    return {
      prefix: 'xmlns',
      local: '',
    };
  }
  const chunks = name.split(':');
  if (chunks.length === 1) {
    return {
      prefix: '',
      local: chunks[0],
    };
  }
  return {
    prefix: chunks[0],
    local: chunks[1],
  };
};

var cssSelectOpts = {
  xmlMode: true,
  adapter: svgoCssSelectAdapter,
};

const attrsHandler = {
  get: (attributes, name) => {
    // eslint-disable-next-line no-prototype-builtins
    if (attributes.hasOwnProperty(name)) {
      return {
        name,
        get value() {
          return attributes[name];
        },
        set value(value) {
          attributes[name] = value;
        },
      };
    }
  },
  set: (attributes, name, attr) => {
    attributes[name] = attr.value;
    return true;
  },
};

var JSAPI = function (data, parentNode) {
  Object.assign(this, data);
  if (this.type === 'element') {
    if (this.attributes == null) {
      this.attributes = {};
    }
    if (this.children == null) {
      this.children = [];
    }
    Object.defineProperty(this, 'class', {
      writable: true,
      configurable: true,
      value: new CSSClassList(this),
    });
    Object.defineProperty(this, 'style', {
      writable: true,
      configurable: true,
      value: new CSSStyleDeclaration(this),
    });
    Object.defineProperty(this, 'parentNode', {
      writable: true,
      value: parentNode,
    });

    // temporary attrs polyfill
    // TODO remove after migration
    const element = this;
    Object.defineProperty(this, 'attrs', {
      configurable: true,
      get() {
        return new Proxy(element.attributes, attrsHandler);
      },
      set(value) {
        const newAttributes = {};
        for (const attr of Object.values(value)) {
          newAttributes[attr.name] = attr.value;
        }
        element.attributes = newAttributes;
      },
    });
  }
};
module.exports = JSAPI;

/**
 * Perform a deep clone of this node.
 *
 * @return {Object} element
 */
JSAPI.prototype.clone = function () {
  const { children, ...nodeData } = this;
  // Deep-clone node data.
  const clonedNode = new JSAPI(JSON.parse(JSON.stringify(nodeData)), null);
  if (children) {
    clonedNode.children = children.map((child) => {
      const clonedChild = child.clone();
      clonedChild.parentNode = clonedNode;
      return clonedChild;
    });
  }
  return clonedNode;
};

/**
 * Determine if item is an element
 * (any, with a specific name or in a names array).
 *
 * @param {String|Array} [param] element name or names arrays
 * @return {Boolean}
 */
JSAPI.prototype.isElem = function (param) {
  if (this.type !== 'element') {
    return false;
  }
  if (param == null) {
    return true;
  }
  if (Array.isArray(param)) {
    return param.includes(this.name);
  }
  return this.name === param;
};

/**
 * Renames an element
 *
 * @param {String} name new element name
 * @return {Object} element
 */
JSAPI.prototype.renameElem = function (name) {
  if (name && typeof name === 'string') this.name = name;

  return this;
};

/**
 * Determine if element is empty.
 *
 * @return {Boolean}
 */
JSAPI.prototype.isEmpty = function () {
  return !this.children || !this.children.length;
};

/**
 * Find the closest ancestor of the current element.
 * @param elemName
 *
 * @return {?Object}
 */
JSAPI.prototype.closestElem = function (elemName) {
  var elem = this;

  while ((elem = elem.parentNode) && !elem.isElem(elemName));

  return elem;
};

/**
 * Changes children by removing elements and/or adding new elements.
 *
 * @param {Number} start Index at which to start changing the children.
 * @param {Number} n Number of elements to remove.
 * @param {Array|Object} [insertion] Elements to add to the children.
 * @return {Array} Removed elements.
 */
JSAPI.prototype.spliceContent = function (start, n, insertion) {
  if (arguments.length < 2) return [];

  if (!Array.isArray(insertion))
    insertion = Array.apply(null, arguments).slice(2);

  insertion.forEach(function (inner) {
    inner.parentNode = this;
  }, this);

  return this.children.splice.apply(
    this.children,
    [start, n].concat(insertion)
  );
};

/**
 * Determine if element has an attribute
 * (any, or by name or by name + value).
 *
 * @param {String} [name] attribute name
 * @param {String} [val] attribute value (will be toString()'ed)
 * @return {Boolean}
 */
JSAPI.prototype.hasAttr = function (name, val) {
  if (this.type !== 'element') {
    return false;
  }
  if (Object.keys(this.attributes).length === 0) {
    return false;
  }
  if (name == null) {
    return true;
  }
  // eslint-disable-next-line no-prototype-builtins
  if (this.attributes.hasOwnProperty(name) === false) {
    return false;
  }
  if (val !== undefined) {
    return this.attributes[name] === val.toString();
  }
  return true;
};

/**
 * Determine if element has an attribute by local name
 * (any, or by name or by name + value).
 *
 * @param {String} [localName] local attribute name
 * @param {Number|String|RegExp|Function} [val] attribute value (will be toString()'ed or executed, otherwise ignored)
 * @return {Boolean}
 */
JSAPI.prototype.hasAttrLocal = function (localName, val) {
  if (!this.attrs || !Object.keys(this.attrs).length) return false;

  if (!arguments.length) return !!this.attrs;

  var callback;

  switch (val != null && val.constructor && val.constructor.name) {
    case 'Number': // same as String
    case 'String':
      callback = stringValueTest;
      break;
    case 'RegExp':
      callback = regexpValueTest;
      break;
    case 'Function':
      callback = funcValueTest;
      break;
    default:
      callback = nameTest;
  }
  return this.someAttr(callback);

  function nameTest(attr) {
    const { local } = parseName(attr.name);
    return local === localName;
  }

  function stringValueTest(attr) {
    const { local } = parseName(attr.name);
    return local === localName && val == attr.value;
  }

  function regexpValueTest(attr) {
    const { local } = parseName(attr.name);
    return local === localName && val.test(attr.value);
  }

  function funcValueTest(attr) {
    const { local } = parseName(attr.name);
    return local === localName && val(attr.value);
  }
};

/**
 * Get a specific attribute from an element
 * (by name or name + value).
 *
 * @param {String} name attribute name
 * @param {String} [val] attribute value (will be toString()'ed)
 * @return {Object|Undefined}
 */
JSAPI.prototype.attr = function (name, val) {
  if (this.hasAttr(name, val)) {
    return this.attrs[name];
  }
};

/**
 * Get computed attribute value from an element
 *
 * @param {String} name attribute name
 * @return {Object|Undefined}
 */
JSAPI.prototype.computedAttr = function (name, val) {
  if (!arguments.length) return;

  for (
    var elem = this;
    elem && (!elem.hasAttr(name) || !elem.attributes[name]);
    elem = elem.parentNode
  );

  if (val != null) {
    return elem ? elem.hasAttr(name, val) : false;
  } else if (elem && elem.hasAttr(name)) {
    return elem.attributes[name];
  }
};

/**
 * Remove a specific attribute.
 *
 * @param {String|Array} name attribute name
 * @param {String} [val] attribute value
 * @return {Boolean}
 */
JSAPI.prototype.removeAttr = function (name, val) {
  if (this.type !== 'element') {
    return false;
  }
  if (arguments.length === 0) {
    return false;
  }
  if (Array.isArray(name)) {
    for (const nameItem of name) {
      this.removeAttr(nameItem, val);
    }
    return false;
  }
  if (this.hasAttr(name, val) === false) {
    return false;
  }
  delete this.attributes[name];
  return true;
};

/**
 * Add attribute.
 *
 * @param {Object} [attr={}] attribute object
 * @return {Object|Boolean} created attribute or false if no attr was passed in
 */
JSAPI.prototype.addAttr = function (attr) {
  attr = attr || {};

  if (attr.name === undefined) return false;

  this.attributes[attr.name] = attr.value;

  if (attr.name === 'class') {
    // newly added class attribute
    this.class.addClassValueHandler();
  }

  if (attr.name === 'style') {
    // newly added style attribute
    this.style.addStyleValueHandler();
  }

  return this.attrs[attr.name];
};

/**
 * Iterates over all attributes.
 *
 * @param {Function} callback callback
 * @param {Object} [context] callback context
 * @return {Boolean} false if there are no any attributes
 */
JSAPI.prototype.eachAttr = function (callback, context) {
  if (this.type !== 'element') {
    return false;
  }
  if (callback == null) {
    return false;
  }
  for (const attr of Object.values(this.attrs)) {
    callback.call(context, attr);
  }
  return true;
};

/**
 * Tests whether some attribute passes the test.
 *
 * @param {Function} callback callback
 * @param {Object} [context] callback context
 * @return {Boolean} false if there are no any attributes
 */
JSAPI.prototype.someAttr = function (callback, context) {
  if (this.type !== 'element') {
    return false;
  }

  for (const attr of Object.values(this.attrs)) {
    if (callback.call(context, attr)) return true;
  }

  return false;
};

/**
 * Evaluate a string of CSS selectors against the element and returns matched elements.
 *
 * @param {String} selectors CSS selector(s) string
 * @return {Array} null if no elements matched
 */
JSAPI.prototype.querySelectorAll = function (selectors) {
  var matchedEls = selectAll(selectors, this, cssSelectOpts);

  return matchedEls.length > 0 ? matchedEls : null;
};

/**
 * Evaluate a string of CSS selectors against the element and returns only the first matched element.
 *
 * @param {String} selectors CSS selector(s) string
 * @return {Array} null if no element matched
 */
JSAPI.prototype.querySelector = function (selectors) {
  return selectOne(selectors, this, cssSelectOpts);
};

/**
 * Test if a selector matches a given element.
 *
 * @param {String} selector CSS selector string
 * @return {Boolean} true if element would be selected by selector string, false if it does not
 */
JSAPI.prototype.matches = function (selector) {
  return is(this, selector, cssSelectOpts);
};


code:
'use strict';

const { visit } = require('../xast.js');

/**
 * Plugins engine.
 *
 * @module plugins
 *
 * @param {Object} ast input ast
 * @param {Object} info extra information
 * @param {Array} plugins plugins object from config
 * @return {Object} output ast
 */
const invokePlugins = (ast, info, plugins, overrides, globalOverrides) => {
  for (const plugin of plugins) {
    const override = overrides == null ? null : overrides[plugin.name];
    if (override === false) {
      continue;
    }
    const params = { ...plugin.params, ...globalOverrides, ...override };

    if (plugin.type === 'perItem') {
      ast = perItem(ast, info, plugin, params);
    }
    if (plugin.type === 'perItemReverse') {
      ast = perItem(ast, info, plugin, params, true);
    }
    if (plugin.type === 'full') {
      if (plugin.active) {
        ast = plugin.fn(ast, params, info);
      }
    }
    if (plugin.type === 'visitor') {
      if (plugin.active) {
        const visitor = plugin.fn(ast, params, info);
        if (visitor != null) {
          visit(ast, visitor);
        }
      }
    }
  }
  return ast;
};
exports.invokePlugins = invokePlugins;

/**
 * Direct or reverse per-item loop.
 *
 * @param {Object} data input data
 * @param {Object} info extra information
 * @param {Array} plugins plugins list to process
 * @param {boolean} [reverse] reverse pass?
 * @return {Object} output data
 */
function perItem(data, info, plugin, params, reverse) {
  function monkeys(items) {
    items.children = items.children.filter(function (item) {
      // reverse pass
      if (reverse && item.children) {
        monkeys(item);
      }
      // main filter
      let kept = true;
      if (plugin.active) {
        kept = plugin.fn(item, params, info) !== false;
      }
      // direct pass
      if (!reverse && item.children) {
        monkeys(item);
      }
      return kept;
    });
    return items;
  }
  return monkeys(data);
}

const createPreset = ({ name, plugins }) => {
  return {
    name,
    type: 'full',
    fn: (ast, params, info) => {
      const { floatPrecision, overrides } = params;
      const globalOverrides = {};
      if (floatPrecision != null) {
        globalOverrides.floatPrecision = floatPrecision;
      }
      if (overrides) {
        for (const [pluginName, override] of Object.entries(overrides)) {
          if (override === true) {
            console.warn(
              `You are trying to enable ${pluginName} which is not part of preset.\n` +
                `Try to put it before or after preset, for example\n\n` +
                `plugins: [\n` +
                `  {\n` +
                `    name: 'preset-default',\n` +
                `  },\n` +
                `  'cleanupListOfValues'\n` +
                `]\n`
            );
          }
        }
      }
      return invokePlugins(ast, info, plugins, overrides, globalOverrides);
    },
  };
};
exports.createPreset = createPreset;


code:
'use strict';

/**
 * @typedef {import('../types').PathDataCommand} PathDataCommand
 */

/**
 * Encode plain SVG data string into Data URI string.
 *
 * @type {(str: string, type?: 'base64' | 'enc' | 'unenc') => string}
 */
exports.encodeSVGDatauri = (str, type) => {
  var prefix = 'data:image/svg+xml';
  if (!type || type === 'base64') {
    // base64
    prefix += ';base64,';
    str = prefix + Buffer.from(str).toString('base64');
  } else if (type === 'enc') {
    // URI encoded
    str = prefix + ',' + encodeURIComponent(str);
  } else if (type === 'unenc') {
    // unencoded
    str = prefix + ',' + str;
  }
  return str;
};

/**
 * Decode SVG Data URI string into plain SVG string.
 *
 * @type {(str: string) => string}
 */
exports.decodeSVGDatauri = (str) => {
  var regexp = /data:image\/svg\+xml(;charset=[^;,]*)?(;base64)?,(.*)/;
  var match = regexp.exec(str);

  // plain string
  if (!match) return str;

  var data = match[3];

  if (match[2]) {
    // base64
    str = Buffer.from(data, 'base64').toString('utf8');
  } else if (data.charAt(0) === '%') {
    // URI encoded
    str = decodeURIComponent(data);
  } else if (data.charAt(0) === '<') {
    // unencoded
    str = data;
  }
  return str;
};

/**
 * @typedef {{
 *   noSpaceAfterFlags?: boolean,
 *   leadingZero?: boolean,
 *   negativeExtraSpace?: boolean
 * }} CleanupOutDataParams
 */

/**
 * Convert a row of numbers to an optimized string view.
 *
 * @example
 * [0, -1, .5, .5] → "0-1 .5.5"
 *
 * @type {(data: Array<number>, params: CleanupOutDataParams, command?: PathDataCommand) => string}
 */
exports.cleanupOutData = (data, params, command) => {
  let str = '';
  let delimiter;
  /**
   * @type {number}
   */
  let prev;

  data.forEach((item, i) => {
    // space delimiter by default
    delimiter = ' ';

    // no extra space in front of first number
    if (i == 0) delimiter = '';

    // no extra space after 'arcto' command flags(large-arc and sweep flags)
    // a20 60 45 0 1 30 20 → a20 60 45 0130 20
    if (params.noSpaceAfterFlags && (command == 'A' || command == 'a')) {
      var pos = i % 7;
      if (pos == 4 || pos == 5) delimiter = '';
    }

    // remove floating-point numbers leading zeros
    // 0.5 → .5
    // -0.5 → -.5
    const itemStr = params.leadingZero
      ? removeLeadingZero(item)
      : item.toString();

    // no extra space in front of negative number or
    // in front of a floating number if a previous number is floating too
    if (
      params.negativeExtraSpace &&
      delimiter != '' &&
      (item < 0 || (itemStr.charAt(0) === '.' && prev % 1 !== 0))
    ) {
      delimiter = '';
    }
    // save prev item value
    prev = item;
    str += delimiter + itemStr;
  });
  return str;
};

/**
 * Remove floating-point numbers leading zero.
 *
 * @example
 * 0.5 → .5
 *
 * @example
 * -0.5 → -.5
 *
 * @type {(num: number) => string}
 */
const removeLeadingZero = (num) => {
  var strNum = num.toString();

  if (0 < num && num < 1 && strNum.charAt(0) === '0') {
    strNum = strNum.slice(1);
  } else if (-1 < num && num < 0 && strNum.charAt(1) === '0') {
    strNum = strNum.charAt(0) + strNum.slice(2);
  }
  return strNum;
};
exports.removeLeadingZero = removeLeadingZero;


code:
export type XastDoctype = {
  type: 'doctype';
  name: string;
  data: {
    doctype: string;
  };
};

export type XastInstruction = {
  type: 'instruction';
  name: string;
  value: string;
};

export type XastComment = {
  type: 'comment';
  value: string;
};

export type XastCdata = {
  type: 'cdata';
  value: string;
};

export type XastText = {
  type: 'text';
  value: string;
};

export type XastElement = {
  type: 'element';
  name: string;
  attributes: Record<string, string>;
  children: Array<XastChild>;
};

export type XastChild =
  | XastDoctype
  | XastInstruction
  | XastComment
  | XastCdata
  | XastText
  | XastElement;

export type XastRoot = {
  type: 'root';
  children: Array<XastChild>;
};

export type XastParent = XastRoot | XastElement;

export type XastNode = XastRoot | XastChild;

export type StringifyOptions = {
  doctypeStart?: string;
  doctypeEnd?: string;
  procInstStart?: string;
  procInstEnd?: string;
  tagOpenStart?: string;
  tagOpenEnd?: string;
  tagCloseStart?: string;
  tagCloseEnd?: string;
  tagShortStart?: string;
  tagShortEnd?: string;
  attrStart?: string;
  attrEnd?: string;
  commentStart?: string;
  commentEnd?: string;
  cdataStart?: string;
  cdataEnd?: string;
  textStart?: string;
  textEnd?: string;
  indent?: number | string;
  regEntities?: RegExp;
  regValEntities?: RegExp;
  encodeEntity?: (char: string) => string;
  pretty?: boolean;
  useShortTags?: boolean;
  eol?: 'lf' | 'crlf';
  finalNewline?: boolean;
};

type VisitorNode<Node> = {
  enter?: (node: Node, parentNode: XastParent) => void | symbol;
  exit?: (node: Node, parentNode: XastParent) => void;
};

type VisitorRoot = {
  enter?: (node: XastRoot, parentNode: null) => void;
  exit?: (node: XastRoot, parentNode: null) => void;
};

export type Visitor = {
  doctype?: VisitorNode<XastDoctype>;
  instruction?: VisitorNode<XastInstruction>;
  comment?: VisitorNode<XastComment>;
  cdata?: VisitorNode<XastCdata>;
  text?: VisitorNode<XastText>;
  element?: VisitorNode<XastElement>;
  root?: VisitorRoot;
};

export type PluginInfo = {
  path?: string;
  multipassCount: number;
};

export type Plugin<Params> = (
  root: XastRoot,
  params: Params,
  info: PluginInfo
) => null | Visitor;

export type Specificity = [number, number, number, number];

export type StylesheetDeclaration = {
  name: string;
  value: string;
  important: boolean;
};

export type StylesheetRule = {
  dynamic: boolean;
  selectors: string;
  specificity: Specificity;
  declarations: Array<StylesheetDeclaration>;
};

export type Stylesheet = {
  rules: Array<StylesheetRule>;
  parents: Map<XastElement, XastParent>;
};

type StaticStyle = {
  type: 'static';
  inherited: boolean;
  value: string;
};

type DynamicStyle = {
  type: 'dynamic';
  inherited: boolean;
};

export type ComputedStyles = Record<string, StaticStyle | DynamicStyle>;

export type PathDataCommand =
  | 'M'
  | 'm'
  | 'Z'
  | 'z'
  | 'L'
  | 'l'
  | 'H'
  | 'h'
  | 'V'
  | 'v'
  | 'C'
  | 'c'
  | 'S'
  | 's'
  | 'Q'
  | 'q'
  | 'T'
  | 't'
  | 'A'
  | 'a';

export type PathDataItem = {
  command: PathDataCommand;
  args: Array<number>;
};


code:
'use strict';

/**
 * @typedef {import('./types').XastNode} XastNode
 * @typedef {import('./types').XastChild} XastChild
 * @typedef {import('./types').XastParent} XastParent
 * @typedef {import('./types').Visitor} Visitor
 */

const { selectAll, selectOne, is } = require('css-select');
const xastAdaptor = require('./svgo/css-select-adapter.js');

const cssSelectOptions = {
  xmlMode: true,
  adapter: xastAdaptor,
};

/**
 * @type {(node: XastNode, selector: string) => Array<XastChild>}
 */
const querySelectorAll = (node, selector) => {
  return selectAll(selector, node, cssSelectOptions);
};
exports.querySelectorAll = querySelectorAll;

/**
 * @type {(node: XastNode, selector: string) => null | XastChild}
 */
const querySelector = (node, selector) => {
  return selectOne(selector, node, cssSelectOptions);
};
exports.querySelector = querySelector;

/**
 * @type {(node: XastChild, selector: string) => boolean}
 */
const matches = (node, selector) => {
  return is(node, selector, cssSelectOptions);
};
exports.matches = matches;

/**
 * @type {(node: XastChild, name: string) => null | XastChild}
 */
const closestByName = (node, name) => {
  let currentNode = node;
  while (currentNode) {
    if (currentNode.type === 'element' && currentNode.name === name) {
      return currentNode;
    }
    // @ts-ignore parentNode is hidden from public usage
    currentNode = currentNode.parentNode;
  }
  return null;
};
exports.closestByName = closestByName;

const visitSkip = Symbol();
exports.visitSkip = visitSkip;

/**
 * @type {(node: XastNode, visitor: Visitor, parentNode?: any) => void}
 */
const visit = (node, visitor, parentNode) => {
  const callbacks = visitor[node.type];
  if (callbacks && callbacks.enter) {
    // @ts-ignore hard to infer
    const symbol = callbacks.enter(node, parentNode);
    if (symbol === visitSkip) {
      return;
    }
  }
  // visit root children
  if (node.type === 'root') {
    // copy children array to not loose cursor when children is spliced
    for (const child of node.children) {
      visit(child, visitor, node);
    }
  }
  // visit element children if still attached to parent
  if (node.type === 'element') {
    if (parentNode.children.includes(node)) {
      for (const child of node.children) {
        visit(child, visitor, node);
      }
    }
  }
  if (callbacks && callbacks.exit) {
    // @ts-ignore hard to infer
    callbacks.exit(node, parentNode);
  }
};
exports.visit = visit;

/**
 * @type {(node: XastChild, parentNode: XastParent) => void}
 */
const detachNodeFromParent = (node, parentNode) => {
  // avoid splice to not break for loops
  parentNode.children = parentNode.children.filter((child) => child !== node);
};
exports.detachNodeFromParent = detachNodeFromParent;


code:
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). (Format adopted after v3.0.0.)

<!-- markdownlint-disable MD024 -->
<!-- markdownlint-disable MD004 -->

## [7.2.0] (2021-03-26)

### Added

- TypeScript typing for `parent` property on `Command` ([#1475])
- TypeScript typing for `.attributeName()` on `Option` ([#1483])
- support information in package ([#1477])

### Changed

- improvements to error messages, README, and tests
- update dependencies

## [7.1.0] (2021-02-15)

### Added

- support for named imports from ECMAScript modules ([#1440])
- add `.cjs` to list of expected script file extensions ([#1449])
- allow using option choices and variadic together ([#1454])

### Fixed

- replace use of deprecated `process.mainModule` ([#1448])
- regression for legacy `command('*')` and call when command line includes options ([#1464])
- regression for `on('command:*', ...)` and call when command line includes unknown options ([#1464])
- display best error for combination of unknown command and unknown option (i.e. unknown command) ([#1464])

### Changed

- make TypeScript typings tests stricter ([#1453])
- improvements to README and tests

## [7.0.0] (2021-01-15)

### Added

- `.enablePositionalOptions()` to let program and subcommand reuse same option ([#1427])
- `.passThroughOptions()` to pass options through to other programs without needing `--` ([#1427])
- `.allowExcessArguments(false)` to show an error message if there are too many command-arguments on command line for the action handler ([#1409])
- `.configureOutput()` to modify use of stdout and stderr or customise display of errors ([#1387])
- use `.addHelpText()` to add text before or after the built-in help, for just current command or also for all subcommands ([#1296])
- enhance Option class ([#1331])
  - allow hiding options from help
  - allow restricting option arguments to a list of choices
  - allow setting how default value is shown in help
- `.createOption()` to support subclassing of automatically created options (like `.createCommand()`) ([#1380])
- refactor the code generating the help into a separate public Help class ([#1365])
  - support sorting subcommands and options in help
  - support specifying wrap width (columns)
  - allow subclassing Help class
  - allow configuring Help class without subclassing

### Changed

- *Breaking:* options are stored safely by default, not as properties on the command ([#1409])
    - this especially affects accessing options on program, use `program.opts()`
    - revert behaviour with `.storeOptionsAsProperties()`
- *Breaking:* action handlers are passed options and command separately ([#1409])
- deprecated callback parameter to `.help()` and `.outputHelp()` (removed from README) ([#1296])
- *Breaking:* errors now displayed using `process.stderr.write()` instead of `console.error()`
- deprecate `.on('--help')` (removed from README) ([#1296])
- initialise the command description to empty string (previously undefined) ([#1365])
- document and annotate deprecated routines ([#1349])

### Fixed

- wrapping bugs in help ([#1365])
  - first line of command description was wrapping two characters early
  - pad width calculation was not including help option and help command
  - pad width calculation was including hidden options and commands
- improve backwards compatibility for custom command event listeners ([#1403])

### Deleted

- *Breaking:* `.passCommandToAction()` ([#1409])
    - no longer needed as action handler is passed options and command
- *Breaking:* "extra arguments" parameter to action handler ([#1409])
    - if being used to detect excess arguments, there is now an error available by setting `.allowExcessArguments(false)`

### Migration Tips

The biggest change is the parsed option values. Previously the options were stored by default as properties on the command object, and now the options are stored separately.

If you wish to restore the old behaviour and get running quickly you can call `.storeOptionsAsProperties()`.
To allow you to move to the new code patterns incrementally, the action handler will be passed the command _twice_,
to match the new "options" and "command" parameters (see below).

**program options**

Use the `.opts()` method to access the options. This is available on any command but is used most with the program.

```js
program.option('-d, --debug');
program.parse();
// Old code before Commander 7
if (program.debug) console.log(`Program name is ${program.name()}`);
```

```js
// New code
const options = program.opts();
if (options.debug) console.log(`Program name is ${program.name()}`);
```

**action handler**

The action handler gets passed a parameter for each command-argument you declared. Previously by default the next parameter was the command object with the options as properties. Now the next two parameters are instead the options and the command. If you
only accessed the options there may be no code changes required.

```js
program
  .command('compress <filename>')
  .option('-t, --trace')
  // Old code before Commander 7
  .action((filename, cmd)) => {
    if (cmd.trace) console.log(`Command name is ${cmd.name()}`);
  });
```

```js
  // New code
  .action((filename, options, command)) => {
    if (options.trace) console.log(`Command name is ${command.name()}`);
  });
```

If you already set `.storeOptionsAsProperties(false)` you may still need to adjust your code.

```js
program
  .command('compress <filename>')
  .storeOptionsAsProperties(false)
  .option('-t, --trace')
  // Old code before Commander 7
  .action((filename, command)) => {
    if (command.opts().trace) console.log(`Command name is ${command.name()}`);
  });
```

```js
   // New code
   .action((filename, options, command)) => {
      if (command.opts().trace) console.log(`Command name is ${command.name()}`);
   });
```

## [7.0.0-2] (2020-12-14)

(Released in 7.0.0)

## [7.0.0-1] (2020-11-21)

(Released in 7.0.0)

## [7.0.0-0] (2020-10-25)

(Released in 7.0.0)

## [6.2.1] (2020-12-13)

### Fixed

- some tests failed if directory path included a space ([1390])

## [6.2.0] (2020-10-25)

### Added

- added 'tsx' file extension for stand-alone executable subcommands ([#1368])
- documented second parameter to `.description()` to describe command arguments ([#1353])
- documentation of special cases with options taking varying numbers of option-arguments ([#1332])
- documentation for terminology ([#1361])

### Fixed

- add missing TypeScript definition for `.addHelpCommand()' ([#1375])
- removed blank line after "Arguments:" in help, to match "Options:" and "Commands:" ([#1360])

### Changed

- update dependencies

## [6.1.0] (2020-08-28)

### Added

- include URL to relevant section of README for error for potential conflict between Command properties and option values ([#1306])
- `.combineFlagAndOptionalValue(false)` to ease upgrade path from older versions of Commander ([#1326])
- allow disabling the built-in help option using `.helpOption(false)` ([#1325])
- allow just some arguments in `argumentDescription` to `.description()` ([#1323])

### Changed

- tidy async test and remove lint override ([#1312])

### Fixed

- executable subcommand launching when script path not known ([#1322])

## [6.0.0] (2020-07-21)

### Added

- add support for variadic options ([#1250])
- allow options to be added with just a short flag ([#1256])
  - *Breaking* the option property has same case as flag. e.g. flag `-n` accessed as `opts().n` (previously uppercase)
- *Breaking* throw an error if there might be a clash between option name and a Command property, with advice on how to resolve ([#1275])

### Fixed

- Options which contain -no- in the middle of the option flag should not be treated as negatable. ([#1301])

## [6.0.0-0] (2020-06-20)

(Released in 6.0.0)

## [5.1.0] (2020-04-25)

### Added

- support for multiple command aliases, the first of which is shown in the auto-generated help ([#531], [#1236])
- configuration support in `addCommand()` for `hidden` and `isDefault` ([#1232])

### Fixed

- omit masked help flags from the displayed help ([#645], [#1247])
- remove old short help flag when change help flags using `helpOption` ([#1248])

### Changed

- remove use of `arguments` to improve auto-generated help in editors ([#1235])
- rename `.command()` configuration `noHelp` to `hidden` (but not remove old support) ([#1232])
- improvements to documentation
- update dependencies
- update tested versions of node
- eliminate lint errors in TypeScript ([#1208])

## [5.0.0] (2020-03-14)

### Added

* support for nested commands with action-handlers ([#1] [#764] [#1149])
* `.addCommand()` for adding a separately configured command ([#764] [#1149])
* allow a non-executable to be set as the default command ([#742] [#1149])
* implicit help command when there are subcommands (previously only if executables) ([#1149])
* customise implicit help command with `.addHelpCommand()` ([#1149])
* display error message for unknown subcommand, by default ([#432] [#1088] [#1149])
* display help for missing subcommand, by default ([#1088] [#1149])
* combined short options as single argument may include boolean flags and value flag and value (e.g. `-a -b -p 80` can be written as `-abp80`) ([#1145])
* `.parseOption()` includes short flag and long flag expansions ([#1145])
* `.helpInformation()` returns help text as a string, previously a private routine ([#1169])
* `.parse()` implicitly uses `process.argv` if arguments not specified ([#1172])
* optionally specify where `.parse()` arguments "from", if not following node conventions ([#512] [#1172])
* suggest help option along with unknown command error ([#1179])
* TypeScript definition for `commands` property of `Command` ([#1184])
* export `program` property ([#1195])
* `createCommand` factory method to simplify subclassing ([#1191])

### Fixed

* preserve argument order in subcommands ([#508] [#962] [#1138])
* do not emit `command:*` for executable subcommands ([#809] [#1149])
* action handler called whether or not there are non-option arguments ([#1062] [#1149])
* combining option short flag and value in single argument now works for subcommands ([#1145])
* only add implicit help command when it will not conflict with other uses of argument ([#1153] [#1149])
* implicit help command works with command aliases ([#948] [#1149])
* options are validated whether or not there is an action handler ([#1149])

### Changed

* *Breaking* `.args` contains command arguments with just recognised options removed ([#1032] [#1138])
* *Breaking* display error if required argument for command is missing ([#995] [#1149])
* tighten TypeScript definition of custom option processing function passed to `.option()` ([#1119])
* *Breaking* `.allowUnknownOption()` ([#802] [#1138])
  * unknown options included in arguments passed to command action handler
  * unknown options included in `.args`
* only recognised option short flags and long flags are expanded (e.g. `-ab` or `--foo=bar`) ([#1145])
* *Breaking* `.parseOptions()` ([#1138])
  * `args` in returned result renamed `operands` and does not include anything after first unknown option
  * `unknown` in returned result has arguments after first unknown option including operands, not just options and values
* *Breaking* `.on('command:*', callback)` and other command events passed (changed) results from `.parseOptions`, i.e. operands and unknown  ([#1138])
* refactor Option from prototype to class ([#1133])
* refactor Command from prototype to class ([#1159])
* changes to error handling ([#1165])
  * throw for author error, not just display message
  * preflight for variadic error
  * add tips to missing subcommand executable
* TypeScript fluent return types changed to be more subclass friendly, return `this` rather than `Command` ([#1180])
* `.parseAsync` returns `Promise<this>` to be consistent with `.parse()` ([#1180])
* update dependencies

### Removed

* removed EventEmitter from TypeScript definition for Command, eliminating implicit peer dependency on `@types/node` ([#1146])
* removed private function `normalize` (the functionality has been integrated into `parseOptions`) ([#1145])
* `parseExpectedArgs` is now private ([#1149])

### Migration Tips

If you use `.on('command:*')` or more complicated tests to detect an unrecognised subcommand, you may be able to delete the code and rely on the default behaviour.

If you use `program.args` or more complicated tests to detect a missing subcommand, you may be able to delete the code and rely on the default behaviour.

If you use `.command('*')` to add a default command, you may be be able to switch to `isDefault:true` with a named command.

If you want to continue combining short options with optional values as though they were boolean flags, set `combineFlagAndOptionalValue(false)`
to expand `-fb` to `-f -b` rather than `-f b`.

## [5.0.0-4] (2020-03-03)

(Released in 5.0.0)

## [5.0.0-3] (2020-02-20)

(Released in 5.0.0)

## [5.0.0-2] (2020-02-10)

(Released in 5.0.0)

## [5.0.0-1] (2020-02-08)

(Released in 5.0.0)

## [5.0.0-0] (2020-02-02)

(Released in 5.0.0)

## Older versions

* [4.x](./changelogs/CHANGELOG-4.md)
* [3.x](./changelogs/CHANGELOG-3.md)
* [2.x](./changelogs/CHANGELOG-2.md)
* [1.x](./changelogs/CHANGELOG-1.md)
* [0.x](./changelogs/CHANGELOG-0.md)

[#1]: https://github.com/tj/commander.js/issues/1
[#432]: https://github.com/tj/commander.js/issues/432
[#508]: https://github.com/tj/commander.js/issues/508
[#512]: https://github.com/tj/commander.js/issues/512
[#531]: https://github.com/tj/commander.js/issues/531
[#645]: https://github.com/tj/commander.js/issues/645
[#742]: https://github.com/tj/commander.js/issues/742
[#764]: https://github.com/tj/commander.js/issues/764
[#802]: https://github.com/tj/commander.js/issues/802
[#809]: https://github.com/tj/commander.js/issues/809
[#948]: https://github.com/tj/commander.js/issues/948
[#962]: https://github.com/tj/commander.js/issues/962
[#995]: https://github.com/tj/commander.js/issues/995
[#1032]: https://github.com/tj/commander.js/issues/1032
[#1062]: https://github.com/tj/commander.js/pull/1062
[#1088]: https://github.com/tj/commander.js/issues/1088
[#1119]: https://github.com/tj/commander.js/pull/1119
[#1133]: https://github.com/tj/commander.js/pull/1133
[#1138]: https://github.com/tj/commander.js/pull/1138
[#1145]: https://github.com/tj/commander.js/pull/1145
[#1146]: https://github.com/tj/commander.js/pull/1146
[#1149]: https://github.com/tj/commander.js/pull/1149
[#1153]: https://github.com/tj/commander.js/issues/1153
[#1159]: https://github.com/tj/commander.js/pull/1159
[#1165]: https://github.com/tj/commander.js/pull/1165
[#1169]: https://github.com/tj/commander.js/pull/1169
[#1172]: https://github.com/tj/commander.js/pull/1172
[#1179]: https://github.com/tj/commander.js/pull/1179
[#1180]: https://github.com/tj/commander.js/pull/1180
[#1184]: https://github.com/tj/commander.js/pull/1184
[#1191]: https://github.com/tj/commander.js/pull/1191
[#1195]: https://github.com/tj/commander.js/pull/1195
[#1208]: https://github.com/tj/commander.js/pull/1208
[#1232]: https://github.com/tj/commander.js/pull/1232
[#1235]: https://github.com/tj/commander.js/pull/1235
[#1236]: https://github.com/tj/commander.js/pull/1236
[#1247]: https://github.com/tj/commander.js/pull/1247
[#1248]: https://github.com/tj/commander.js/pull/1248
[#1250]: https://github.com/tj/commander.js/pull/1250
[#1256]: https://github.com/tj/commander.js/pull/1256
[#1275]: https://github.com/tj/commander.js/pull/1275
[#1296]: https://github.com/tj/commander.js/pull/1296
[#1301]: https://github.com/tj/commander.js/issues/1301
[#1306]: https://github.com/tj/commander.js/pull/1306
[#1312]: https://github.com/tj/commander.js/pull/1312
[#1322]: https://github.com/tj/commander.js/pull/1322
[#1323]: https://github.com/tj/commander.js/pull/1323
[#1325]: https://github.com/tj/commander.js/pull/1325
[#1326]: https://github.com/tj/commander.js/pull/1326
[#1331]: https://github.com/tj/commander.js/pull/1331
[#1332]: https://github.com/tj/commander.js/pull/1332
[#1349]: https://github.com/tj/commander.js/pull/1349
[#1353]: https://github.com/tj/commander.js/pull/1353
[#1360]: https://github.com/tj/commander.js/pull/1360
[#1361]: https://github.com/tj/commander.js/pull/1361
[#1365]: https://github.com/tj/commander.js/pull/1365
[#1368]: https://github.com/tj/commander.js/pull/1368
[#1375]: https://github.com/tj/commander.js/pull/1375
[#1380]: https://github.com/tj/commander.js/pull/1380
[#1387]: https://github.com/tj/commander.js/pull/1387
[#1390]: https://github.com/tj/commander.js/pull/1390
[#1403]: https://github.com/tj/commander.js/pull/1403
[#1409]: https://github.com/tj/commander.js/pull/1409
[#1427]: https://github.com/tj/commander.js/pull/1427
[#1440]: https://github.com/tj/commander.js/pull/1440
[#1448]: https://github.com/tj/commander.js/pull/1448
[#1449]: https://github.com/tj/commander.js/pull/1449
[#1453]: https://github.com/tj/commander.js/pull/1453
[#1454]: https://github.com/tj/commander.js/pull/1454
[#1464]: https://github.com/tj/commander.js/pull/1464
[#1475]: https://github.com/tj/commander.js/pull/1475
[#1477]: https://github.com/tj/commander.js/pull/1477
[#1483]: https://github.com/tj/commander.js/pull/1483

[Unreleased]: https://github.com/tj/commander.js/compare/master...develop
[7.2.0]: https://github.com/tj/commander.js/compare/v7.1.0...v7.2.0
[7.1.0]: https://github.com/tj/commander.js/compare/v7.0.0...v7.1.0
[7.0.0]: https://github.com/tj/commander.js/compare/v6.2.1...v7.0.0
[7.0.0-2]: https://github.com/tj/commander.js/compare/v7.0.0-1...v7.0.0-2
[7.0.0-1]: https://github.com/tj/commander.js/compare/v7.0.0-0...v7.0.0-1
[7.0.0-0]: https://github.com/tj/commander.js/compare/v6.2.0...v7.0.0-0
[6.2.1]: https://github.com/tj/commander.js/compare/v6.2.0..v6.2.1
[6.2.0]: https://github.com/tj/commander.js/compare/v6.1.0..v6.2.0
[6.1.0]: https://github.com/tj/commander.js/compare/v6.0.0..v6.1.0
[6.0.0]: https://github.com/tj/commander.js/compare/v5.1.0..v6.0.0
[6.0.0-0]: https://github.com/tj/commander.js/compare/v5.1.0..v6.0.0-0
[5.1.0]: https://github.com/tj/commander.js/compare/v5.0.0..v5.1.0
[5.0.0]: https://github.com/tj/commander.js/compare/v4.1.1..v5.0.0
[5.0.0-4]: https://github.com/tj/commander.js/compare/v5.0.0-3..v5.0.0-4
[5.0.0-3]: https://github.com/tj/commander.js/compare/v5.0.0-2..v5.0.0-3
[5.0.0-2]: https://github.com/tj/commander.js/compare/v5.0.0-1..v5.0.0-2
[5.0.0-1]: https://github.com/tj/commander.js/compare/v5.0.0-0..v5.0.0-1
[5.0.0-0]: https://github.com/tj/commander.js/compare/v4.1.1..v5.0.0-0