code:
# ES Module Lexer

[![Build Status][actions-image]][actions-url]

A JS module syntax lexer used in [es-module-shims](https://github.com/guybedford/es-module-shims).

Outputs the list of exports and locations of import specifiers, including dynamic import and import meta handling.

Supports new syntax features including import attributes and source phase imports.

A very small single JS file (4KiB gzipped) that includes inlined Web Assembly for very fast source analysis of ECMAScript module syntax only.

For an example of the performance, Angular 1 (720KiB) is fully parsed in 5ms, in comparison to the fastest JS parser, Acorn which takes over 100ms.

_Comprehensively handles the JS language grammar while remaining small and fast. - ~10ms per MB of JS cold and ~5ms per MB of JS warm, [see benchmarks](#benchmarks) for more info._

> [Built with](https://github.com/guybedford/es-module-lexer/blob/main/chompfile.toml) [Chomp](https://chompbuild.com/)

### Usage

```
npm install es-module-lexer
```

See [types/lexer.d.ts](types/lexer.d.ts) for the type definitions.

For use in CommonJS:

```js
const { init, parse } = require('es-module-lexer');

(async () => {
  // either await init, or call parse asynchronously
  // this is necessary for the Web Assembly boot
  await init;

  const source = 'export var p = 5';
  const [imports, exports] = parse(source);

  // Returns "p"
  source.slice(exports[0].s, exports[0].e);
  // Returns "p"
  source.slice(exports[0].ls, exports[0].le);
})();
```

An ES module version is also available:

```js
import { init, parse } from 'es-module-lexer';

(async () => {
  await init;

  const source = `
    import { name } from 'mod\\u1011';
    import json from './json.json' assert { type: 'json' }
    export var p = 5;
    export function q () {

    };
    export { x as 'external name' } from 'external';

    // Comments provided to demonstrate edge cases
    import /*comment!*/ (  'asdf', { assert: { type: 'json' }});
    import /*comment!*/.meta.asdf;

    // Source phase imports:
    import source mod from './mod.wasm';
    import.source('./mod.wasm);
  `;

  const [imports, exports] = parse(source, 'optional-sourcename');

  // Returns "modထ"
  imports[0].n
  // Returns "mod\u1011"
  source.slice(imports[0].s, imports[0].e);
  // "s" = start
  // "e" = end

  // Returns "import { name } from 'mod'"
  source.slice(imports[0].ss, imports[0].se);
  // "ss" = statement start
  // "se" = statement end

  // Returns "{ type: 'json' }"
  source.slice(imports[1].a, imports[1].se);
  // "a" = assert, -1 for no assertion

  // Returns "external"
  source.slice(imports[2].s, imports[2].e);

  // Returns "p"
  source.slice(exports[0].s, exports[0].e);
  // Returns "p"
  source.slice(exports[0].ls, exports[0].le);
  // Returns "q"
  source.slice(exports[1].s, exports[1].e);
  // Returns "q"
  source.slice(exports[1].ls, exports[1].le);
  // Returns "'external name'"
  source.slice(exports[2].s, exports[2].e);
  // Returns -1
  exports[2].ls;
  // Returns -1
  exports[2].le;

  // Import type is provided by `t` value
  // (1 for static, 2, for dynamic)
  // Returns true
  imports[2].t == 2;

  // Returns "asdf" (only for string literal dynamic imports)
  imports[2].n
  // Returns "import /*comment!*/ (  'asdf', { assert: { type: 'json' } })"
  source.slice(imports[3].ss, imports[3].se);
  // Returns "'asdf'"
  source.slice(imports[3].s, imports[3].e);
  // Returns "(  'asdf', { assert: { type: 'json' } })"
  source.slice(imports[3].d, imports[3].se);
  // Returns "{ assert: { type: 'json' } }"
  source.slice(imports[3].a, imports[3].se - 1);

  // For non-string dynamic import expressions:
  // - n will be undefined
  // - a is currently -1 even if there is an assertion
  // - e is currently the character before the closing )

  // For nested dynamic imports, the se value of the outer import is -1 as end tracking does not
  // currently support nested dynamic immports

  // import.meta is indicated by imports[3].d === -2
  // Returns true
  imports[4].d === -2;
  // Returns "import /*comment!*/.meta"
  source.slice(imports[4].s, imports[4].e);
  // ss and se are the same for import meta

  // Returns "'./mod.wasm'"
  source.slice(imports[5].s, imports[5].e);

  // Import type 4 and 5 for static and dynamic source phase
  imports[5].t === 4;
  imports[6].t === 5;
})();
```

### CSP asm.js Build

The default version of the library uses Wasm and (safe) eval usage for performance and a minimal footprint.

Neither of these represent security escalation possibilities since there are no execution string injection vectors, but that can still violate existing CSP policies for applications.

For a version that works with CSP eval disabled, use the `es-module-lexer/js` build:

```js
import { parse } from 'es-module-lexer/js';
```

Instead of Web Assembly, this uses an asm.js build which is almost as fast as the Wasm version ([see benchmarks below](#benchmarks)).

### Escape Sequences

To handle escape sequences in specifier strings, the `.n` field of imported specifiers will be provided where possible.

For dynamic import expressions, this field will be empty if not a valid JS string.

### Facade Detection

Facade modules that only use import / export syntax can be detected via the third return value:

```js
const [,, facade] = parse(`
  export * from 'external';
  import * as ns from 'external2';
  export { a as b } from 'external3';
  export { ns };
`);
facade === true;
```

### ESM Detection

Modules that uses ESM syntaxes can be detected via the fourth return value:

```js
const [,,, hasModuleSyntax] = parse(`
  export {}
`);
hasModuleSyntax === true;
```

Dynamic imports are ignored since they can be used in Non-ESM files.

```js
const [,,, hasModuleSyntax] = parse(`
  import('./foo.js')
`);
hasModuleSyntax === false;
```

### Environment Support

Node.js 10+, and [all browsers with Web Assembly support](https://caniuse.com/#feat=wasm).

### Grammar Support

* Token state parses all line comments, block comments, strings, template strings, blocks, parens and punctuators.
* Division operator / regex token ambiguity is handled via backtracking checks against punctuator prefixes, including closing brace or paren backtracking.
* Always correctly parses valid JS source, but may parse invalid JS source without errors.

### Limitations

The lexing approach is designed to deal with the full language grammar including RegEx / division operator ambiguity through backtracking and paren / brace tracking.

The only limitation to the reduced parser is that the "exports" list may not correctly gather all export identifiers in the following edge cases:

```js
// Only "a" is detected as an export, "q" isn't
export var a = 'asdf', q = z;

// "b" is not detected as an export
export var { a: b } = asdf;
```

The above cases are handled gracefully in that the lexer will keep going fine, it will just not properly detect the export names above.

### Benchmarks

Benchmarks can be run with `npm run bench`.

Current results for a high spec machine:

#### Wasm Build

```
Module load time
> 5ms
Cold Run, All Samples
test/samples/*.js (3123 KiB)
> 18ms

Warm Runs (average of 25 runs)
test/samples/angular.js (739 KiB)
> 3ms
test/samples/angular.min.js (188 KiB)
> 1ms
test/samples/d3.js (508 KiB)
> 3ms
test/samples/d3.min.js (274 KiB)
> 2ms
test/samples/magic-string.js (35 KiB)
> 0ms
test/samples/magic-string.min.js (20 KiB)
> 0ms
test/samples/rollup.js (929 KiB)
> 4.32ms
test/samples/rollup.min.js (429 KiB)
> 2.16ms

Warm Runs, All Samples (average of 25 runs)
test/samples/*.js (3123 KiB)
> 14.16ms
```

#### JS Build (asm.js)

```
Module load time
> 2ms
Cold Run, All Samples
test/samples/*.js (3123 KiB)
> 34ms

Warm Runs (average of 25 runs)
test/samples/angular.js (739 KiB)
> 3ms
test/samples/angular.min.js (188 KiB)
> 1ms
test/samples/d3.js (508 KiB)
> 3ms
test/samples/d3.min.js (274 KiB)
> 2ms
test/samples/magic-string.js (35 KiB)
> 0ms
test/samples/magic-string.min.js (20 KiB)
> 0ms
test/samples/rollup.js (929 KiB)
> 5ms
test/samples/rollup.min.js (429 KiB)
> 3.04ms

Warm Runs, All Samples (average of 25 runs)
test/samples/*.js (3123 KiB)
> 17.12ms
```

### Building

This project uses [Chomp](https://chompbuild.com) for building.

With Chomp installed, download the WASI SDK 12.0 from https://github.com/WebAssembly/wasi-sdk/releases/tag/wasi-sdk-12.

- [Linux](https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-12/wasi-sdk-12.0-linux.tar.gz)
- [Windows (MinGW)](https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-12/wasi-sdk-12.0-mingw.tar.gz)
- [macOS](https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-12/wasi-sdk-12.0-macos.tar.gz)

Locate the WASI-SDK as a sibling folder, or customize the path via the `WASI_PATH` environment variable.

Emscripten emsdk is also assumed to be a sibling folder or via the `EMSDK_PATH` environment variable.

Example setup:

```
git clone https://github.com:guybedford/es-module-lexer
git clone https://github.com/emscripten-core/emsdk
cd emsdk
git checkout 1.40.1-fastcomp
./emsdk install 1.40.1-fastcomp
cd ..
wget https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-12/wasi-sdk-12.0-linux.tar.gz
gunzip wasi-sdk-12.0-linux.tar.gz
tar -xf wasi-sdk-12.0-linux.tar
mv wasi-sdk-12.0-linux.tar wasi-sdk-12.0
cargo install chompbuild
cd es-module-lexer
chomp test
```

For the `asm.js` build, git clone `emsdk` from  is assumed to be a sibling folder as well.

### License

MIT

[actions-image]: https://github.com/guybedford/es-module-lexer/actions/workflows/build.yml/badge.svg
[actions-url]: https://github.com/guybedford/es-module-lexer/actions/workflows/build.yml


code:
let source, pos, end,
  openTokenDepth,
  lastTokenPos,
  openTokenPosStack,
  openClassPosStack,
  curDynamicImport,
  templateStackDepth,
  facade,
  lastSlashWasDivision,
  nextBraceIsClass,
  templateDepth,
  templateStack,
  imports,
  exports,
  name;

function addImport (ss, s, e, d) {
  const impt = { ss, se: d === -2 ? e : d === -1 ? e + 1 : 0, s, e, d, a: -1, n: undefined };
  imports.push(impt);
  return impt;
}

function addExport (s, e, ls, le) {
  exports.push({
    s,
    e,
    ls,
    le,
    n: s[0] === '"' ? readString(s, '"') : s[0] === "'" ? readString(s, "'") : source.slice(s, e),
    ln: ls[0] === '"' ? readString(ls, '"') : ls[0] === "'" ? readString(ls, "'") : source.slice(ls, le)
  });
}

function readName (impt) {
  let { d, s } = impt;
  if (d !== -1)
    s++;
  impt.n = readString(s, source.charCodeAt(s - 1));
}

// Note: parsing is based on the _assumption_ that the source is already valid
export function parse (_source, _name) {
  openTokenDepth = 0;
  curDynamicImport = null;
  templateDepth = -1;
  lastTokenPos = -1;
  lastSlashWasDivision = false;
  templateStack = Array(1024);
  templateStackDepth = 0;
  openTokenPosStack = Array(1024);
  openClassPosStack = Array(1024);
  nextBraceIsClass = false;
  facade = true;
  name = _name || '@';

  imports = [];
  exports = [];

  source = _source;
  pos = -1;
  end = source.length - 1;
  let ch = 0;

  // start with a pure "module-only" parser
  m: while (pos++ < end) {
    ch = source.charCodeAt(pos);

    if (ch === 32 || ch < 14 && ch > 8)
      continue;

    switch (ch) {
      case 101/*e*/:
        if (openTokenDepth === 0 && keywordStart(pos) && source.startsWith('xport', pos + 1)) {
          tryParseExportStatement();
          // export might have been a non-pure declaration
          if (!facade) {
            lastTokenPos = pos;
            break m;
          }
        }
        break;
      case 105/*i*/:
        if (keywordStart(pos) && source.startsWith('mport', pos + 1))
          tryParseImportStatement();
        break;
      case 59/*;*/:
        break;
      case 47/*/*/: {
        const next_ch = source.charCodeAt(pos + 1);
        if (next_ch === 47/*/*/) {
          lineComment();
          // dont update lastToken
          continue;
        }
        else if (next_ch === 42/***/) {
          blockComment(true);
          // dont update lastToken
          continue;
        }
        // fallthrough
      }
      default:
        // as soon as we hit a non-module token, we go to main parser
        facade = false;
        pos--;
        break m;
    }
    lastTokenPos = pos;
  }

  while (pos++ < end) {
    ch = source.charCodeAt(pos);

    if (ch === 32 || ch < 14 && ch > 8)
      continue;

    switch (ch) {
      case 101/*e*/:
        if (openTokenDepth === 0 && keywordStart(pos) && source.startsWith('xport', pos + 1))
          tryParseExportStatement();
        break;
      case 105/*i*/:
        if (keywordStart(pos) && source.startsWith('mport', pos + 1))
          tryParseImportStatement();
        break;
      case 99/*c*/:
        if (keywordStart(pos) && source.startsWith('lass', pos + 1) && isBrOrWs(source.charCodeAt(pos + 5)))
          nextBraceIsClass = true;
        break;
      case 40/*(*/:
        openTokenPosStack[openTokenDepth++] = lastTokenPos;
        break;
      case 41/*)*/:
        if (openTokenDepth === 0)
          syntaxError();
        openTokenDepth--;
        if (curDynamicImport && curDynamicImport.d === openTokenPosStack[openTokenDepth]) {
          if (curDynamicImport.e === 0)
            curDynamicImport.e = pos;
          curDynamicImport.se = pos;
          curDynamicImport = null;
        }
        break;
      case 123/*{*/:
        // dynamic import followed by { is not a dynamic import (so remove)
        // this is a sneaky way to get around { import () {} } v { import () }
        // block / object ambiguity without a parser (assuming source is valid)
        if (source.charCodeAt(lastTokenPos) === 41/*)*/ && imports.length && imports[imports.length - 1].e === lastTokenPos) {
          imports.pop();
        }
        openClassPosStack[openTokenDepth] = nextBraceIsClass;
        nextBraceIsClass = false;
        openTokenPosStack[openTokenDepth++] = lastTokenPos;
        break;
      case 125/*}*/:
        if (openTokenDepth === 0)
          syntaxError();
        if (openTokenDepth-- === templateDepth) {
          templateDepth = templateStack[--templateStackDepth];
          templateString();
        }
        else {
          if (templateDepth !== -1 && openTokenDepth < templateDepth)
            syntaxError();
        }
        break;
      case 39/*'*/:
      case 34/*"*/:
        stringLiteral(ch);
        break;
      case 47/*/*/: {
        const next_ch = source.charCodeAt(pos + 1);
        if (next_ch === 47/*/*/) {
          lineComment();
          // dont update lastToken
          continue;
        }
        else if (next_ch === 42/***/) {
          blockComment(true);
          // dont update lastToken
          continue;
        }
        else {
          // Division / regex ambiguity handling based on checking backtrack analysis of:
          // - what token came previously (lastToken)
          // - if a closing brace or paren, what token came before the corresponding
          //   opening brace or paren (lastOpenTokenIndex)
          const lastToken = source.charCodeAt(lastTokenPos);
          const lastExport = exports[exports.length - 1];
          if (isExpressionPunctuator(lastToken) &&
              !(lastToken === 46/*.*/ && (source.charCodeAt(lastTokenPos - 1) >= 48/*0*/ && source.charCodeAt(lastTokenPos - 1) <= 57/*9*/)) &&
              !(lastToken === 43/*+*/ && source.charCodeAt(lastTokenPos - 1) === 43/*+*/) && !(lastToken === 45/*-*/ && source.charCodeAt(lastTokenPos - 1) === 45/*-*/) ||
              lastToken === 41/*)*/ && isParenKeyword(openTokenPosStack[openTokenDepth]) ||
              lastToken === 125/*}*/ && (isExpressionTerminator(openTokenPosStack[openTokenDepth]) || openClassPosStack[openTokenDepth]) ||
              lastToken === 47/*/*/ && lastSlashWasDivision ||
              isExpressionKeyword(lastTokenPos) ||
              !lastToken) {
            regularExpression();
            lastSlashWasDivision = false;
          }
          else if (lastExport && lastTokenPos >= lastExport.s && lastTokenPos <= lastExport.e) {
            // export default /some-regexp/
            regularExpression();
            lastSlashWasDivision = false;
          }
          else {
            lastSlashWasDivision = true;
          }
        }
        break;
      }
      case 96/*`*/:
        templateString();
        break;
    }
    lastTokenPos = pos;
  }

  if (templateDepth !== -1 || openTokenDepth)
    syntaxError();

  return [imports, exports, facade];
}

function tryParseImportStatement () {
  const startPos = pos;

  pos += 6;

  let ch = commentWhitespace(true);

  switch (ch) {
    // dynamic import
    case 40/*(*/:
      openTokenPosStack[openTokenDepth++] = startPos;
      if (source.charCodeAt(lastTokenPos) === 46/*.*/)
        return;
      // dynamic import indicated by positive d
      const impt = addImport(startPos, pos + 1, 0, startPos);
      curDynamicImport = impt;
      // try parse a string, to record a safe dynamic import string
      pos++;
      ch = commentWhitespace(true);
      if (ch === 39/*'*/ || ch === 34/*"*/) {
        stringLiteral(ch);
      }
      else {
        pos--;
        return;
      }
      pos++;
      ch = commentWhitespace(true);
      if (ch === 44/*,*/) {
        impt.e = pos;
        pos++;
        ch = commentWhitespace(true);
        impt.a = pos;
        readName(impt);
        pos--;
      }
      else if (ch === 41/*)*/) {
        openTokenDepth--;
        impt.e = pos;
        impt.se = pos;
        readName(impt);
      }
      else {
        pos--;
      }
      return;
    // import.meta
    case 46/*.*/:
      pos++;
      ch = commentWhitespace(true);
      // import.meta indicated by d === -2
      if (ch === 109/*m*/ && source.startsWith('eta', pos + 1) && source.charCodeAt(lastTokenPos) !== 46/*.*/)
        addImport(startPos, startPos, pos + 4, -2);
      return;

    default:
      // no space after "import" -> not an import keyword
      if (pos === startPos + 6)
        break;
    case 34/*"*/:
    case 39/*'*/:
    case 123/*{*/:
    case 42/***/:
      // import statement only permitted at base-level
      if (openTokenDepth !== 0) {
        pos--;
        return;
      }
      while (pos < end) {
        ch = source.charCodeAt(pos);
        if (ch === 39/*'*/ || ch === 34/*"*/) {
          readImportString(startPos, ch);
          return;
        }
        pos++;
      }
      syntaxError();
  }
}

function tryParseExportStatement () {
  const sStartPos = pos;
  const prevExport = exports.length;

  pos += 6;

  const curPos = pos;

  let ch = commentWhitespace(true);

  if (pos === curPos && !isPunctuator(ch))
    return;

  switch (ch) {
    // export default ...
    case 100/*d*/:
      addExport(pos, pos + 7, -1, -1);
      return;

    // export async? function*? name () {
    case 97/*a*/:
      pos += 5;
      commentWhitespace(true);
    // fallthrough
    case 102/*f*/:
      pos += 8;
      ch = commentWhitespace(true);
      if (ch === 42/***/) {
        pos++;
        ch = commentWhitespace(true);
      }
      const startPos = pos;
      ch = readToWsOrPunctuator(ch);
      addExport(startPos, pos, startPos, pos);
      pos--;
      return;

    // export class name ...
    case 99/*c*/:
      if (source.startsWith('lass', pos + 1) && isBrOrWsOrPunctuatorNotDot(source.charCodeAt(pos + 5))) {
        pos += 5;
        ch = commentWhitespace(true);
        const startPos = pos;
        ch = readToWsOrPunctuator(ch);
        addExport(startPos, pos, startPos, pos);
        pos--;
        return;
      }
      pos += 2;
    // fallthrough

    // export var/let/const name = ...(, name = ...)+
    case 118/*v*/:
    case 109/*l*/:
      // destructured initializations not currently supported (skipped for { or [)
      // also, lexing names after variable equals is skipped (export var p = function () { ... }, q = 5 skips "q")
      pos += 2;
      facade = false;
      do {
        pos++;
        ch = commentWhitespace(true);
        const startPos = pos;
        ch = readToWsOrPunctuator(ch);
        // dont yet handle [ { destructurings
        if (ch === 123/*{*/ || ch === 91/*[*/) {
          pos--;
          return;
        }
        if (pos === startPos)
          return;
        addExport(startPos, pos, startPos, pos);
        ch = commentWhitespace(true);
        if (ch === 61/*=*/) {
          pos--;
          return;
        }
      } while (ch === 44/*,*/);
      pos--;
      return;

    // export {...}
    case 123/*{*/:
      pos++;
      ch = commentWhitespace(true);
      while (true) {
        const startPos = pos;
        readToWsOrPunctuator(ch);
        const endPos = pos;
        commentWhitespace(true);
        ch = readExportAs(startPos, endPos);
        // ,
        if (ch === 44/*,*/) {
          pos++;
          ch = commentWhitespace(true);
        }
        if (ch === 125/*}*/)
          break;
        if (pos === startPos)
          return syntaxError();
        if (pos > end)
          return syntaxError();
      }
      pos++;
      ch = commentWhitespace(true);
    break;

    // export *
    // export * as X
    case 42/***/:
      pos++;
      commentWhitespace(true);
      ch = readExportAs(pos, pos);
      ch = commentWhitespace(true);
    break;
  }

  // from ...
  if (ch === 102/*f*/ && source.startsWith('rom', pos + 1)) {
    pos += 4;
    readImportString(sStartPos, commentWhitespace(true));

    // There were no local names.
    for (let i = prevExport; i < exports.length; ++i) {
      exports[i].ls = exports[i].le = -1;
      exports[i].ln = undefined;
    }
  }
  else {
    pos--;
  }
}

/*
 * Ported from Acorn
 *
 * MIT License

 * Copyright (C) 2012-2020 by various contributors (see AUTHORS)

 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:

 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
let acornPos;
function readString (start, quote) {
  acornPos = start;
  let out = '', chunkStart = acornPos;
  for (;;) {
    if (acornPos >= source.length) syntaxError();
    const ch = source.charCodeAt(acornPos);
    if (ch === quote) break;
    if (ch === 92) { // '\'
      out += source.slice(chunkStart, acornPos);
      out += readEscapedChar();
      chunkStart = acornPos;
    }
    else if (ch === 0x2028 || ch === 0x2029) {
      ++acornPos;
    }
    else {
      if (isBr(ch)) syntaxError();
      ++acornPos;
    }
  }
  out += source.slice(chunkStart, acornPos++);
  return out;
}

// Used to read escaped characters

function readEscapedChar () {
  let ch = source.charCodeAt(++acornPos);
  ++acornPos;
  switch (ch) {
    case 110: return '\n'; // 'n' -> '\n'
    case 114: return '\r'; // 'r' -> '\r'
    case 120: return String.fromCharCode(readHexChar(2)); // 'x'
    case 117: return readCodePointToString(); // 'u'
    case 116: return '\t'; // 't' -> '\t'
    case 98: return '\b'; // 'b' -> '\b'
    case 118: return '\u000b'; // 'v' -> '\u000b'
    case 102: return '\f'; // 'f' -> '\f'
    case 13: if (source.charCodeAt(acornPos) === 10) ++acornPos; // '\r\n'
    case 10: // ' \n'
      return '';
    case 56:
    case 57:
      syntaxError();
    default:
      if (ch >= 48 && ch <= 55) {
        let octalStr = source.substr(acornPos - 1, 3).match(/^[0-7]+/)[0];
        let octal = parseInt(octalStr, 8);
        if (octal > 255) {
          octalStr = octalStr.slice(0, -1);
          octal = parseInt(octalStr, 8);
        }
        acornPos += octalStr.length - 1;
        ch = source.charCodeAt(acornPos);
        if (octalStr !== '0' || ch === 56 || ch === 57)
          syntaxError();
        return String.fromCharCode(octal);
      }
      if (isBr(ch)) {
        // Unicode new line characters after \ get removed from output in both
        // template literals and strings
        return '';
      }
      return String.fromCharCode(ch);
  }
}

// Used to read character escape sequences ('\x', '\u', '\U').

function readHexChar (len) {
  const start = acornPos;
  let total = 0, lastCode = 0;
  for (let i = 0; i < len; ++i, ++acornPos) {
    let code = source.charCodeAt(acornPos), val;

    if (code === 95) {
      if (lastCode === 95 || i === 0) syntaxError();
      lastCode = code;
      continue;
    }

    if (code >= 97) val = code - 97 + 10; // a
    else if (code >= 65) val = code - 65 + 10; // A
    else if (code >= 48 && code <= 57) val = code - 48; // 0-9
    else break;
    if (val >= 16) break;
    lastCode = code;
    total = total * 16 + val;
  }

  if (lastCode === 95 || acornPos - start !== len) syntaxError();

  return total;
}

// Read a string value, interpreting backslash-escapes.

function readCodePointToString () {
  const ch = source.charCodeAt(acornPos);
  let code;
  if (ch === 123) { // '{'
    ++acornPos;
    code = readHexChar(source.indexOf('}', acornPos) - acornPos);
    ++acornPos;
    if (code > 0x10FFFF) syntaxError();
  } else {
    code = readHexChar(4);
  }
  // UTF-16 Decoding
  if (code <= 0xFFFF) return String.fromCharCode(code);
  code -= 0x10000;
  return String.fromCharCode((code >> 10) + 0xD800, (code & 1023) + 0xDC00);
}

/*
 * </ Acorn Port>
 */

function readExportAs (startPos, endPos) {
  let ch = source.charCodeAt(pos);
  let ls = startPos, le = endPos;
  if (ch === 97 /*a*/) {
    pos += 2;
    ch = commentWhitespace(true);
    startPos = pos;
    readToWsOrPunctuator(ch);
    endPos = pos;
    ch = commentWhitespace(true);
  }
  if (pos !== startPos)
    addExport(startPos, endPos, ls, le);
  return ch;
}

function readImportString (ss, ch) {
  const startPos = pos + 1;
  if (ch === 39/*'*/ || ch === 34/*"*/) {
    stringLiteral(ch);
  }
  else {
    syntaxError();
    return;
  }
  const impt = addImport(ss, startPos, pos, -1);
  readName(impt);
  pos++;
  ch = commentWhitespace(false);
  if (ch !== 97/*a*/ || !source.startsWith('ssert', pos + 1)) {
    pos--;
    return;
  }
  const assertIndex = pos;

  pos += 6;
  ch = commentWhitespace(true);
  if (ch !== 123/*{*/) {
    pos = assertIndex;
    return;
  }
  const assertStart = pos;
  do {
    pos++;
    ch = commentWhitespace(true);
    if (ch === 39/*'*/ || ch === 34/*"*/) {
      stringLiteral(ch);
      pos++;
      ch = commentWhitespace(true);
    }
    else {
      ch = readToWsOrPunctuator(ch);
    }
    if (ch !== 58/*:*/) {
      pos = assertIndex;
      return;
    }
    pos++;
    ch = commentWhitespace(true);
    if (ch === 39/*'*/ || ch === 34/*"*/) {
      stringLiteral(ch);
    }
    else {
      pos = assertIndex;
      return;
    }
    pos++;
    ch = commentWhitespace(true);
    if (ch === 44/*,*/) {
      pos++;
      ch = commentWhitespace(true);
      if (ch === 125/*}*/)
        break;
      continue;
    }
    if (ch === 125/*}*/)
      break;
    pos = assertIndex;
    return;
  } while (true);
  impt.a = assertStart;
  impt.se = pos + 1;
}

function commentWhitespace (br) {
  let ch;
  do {
    ch = source.charCodeAt(pos);
    if (ch === 47/*/*/) {
      const next_ch = source.charCodeAt(pos + 1);
      if (next_ch === 47/*/*/)
        lineComment();
      else if (next_ch === 42/***/)
        blockComment(br);
      else
        return ch;
    }
    else if (br ? !isBrOrWs(ch): !isWsNotBr(ch)) {
      return ch;
    }
  } while (pos++ < end);
  return ch;
}

function templateString () {
  while (pos++ < end) {
    const ch = source.charCodeAt(pos);
    if (ch === 36/*$*/ && source.charCodeAt(pos + 1) === 123/*{*/) {
      pos++;
      templateStack[templateStackDepth++] = templateDepth;
      templateDepth = ++openTokenDepth;
      return;
    }
    if (ch === 96/*`*/)
      return;
    if (ch === 92/*\*/)
      pos++;
  }
  syntaxError();
}

function blockComment (br) {
  pos++;
  while (pos++ < end) {
    const ch = source.charCodeAt(pos);
    if (!br && isBr(ch))
      return;
    if (ch === 42/***/ && source.charCodeAt(pos + 1) === 47/*/*/) {
      pos++;
      return;
    }
  }
}

function lineComment () {
  while (pos++ < end) {
    const ch = source.charCodeAt(pos);
    if (ch === 10/*\n*/ || ch === 13/*\r*/)
      return;
  }
}

function stringLiteral (quote) {
  while (pos++ < end) {
    let ch = source.charCodeAt(pos);
    if (ch === quote)
      return;
    if (ch === 92/*\*/) {
      ch = source.charCodeAt(++pos);
      if (ch === 13/*\r*/ && source.charCodeAt(pos + 1) === 10/*\n*/)
        pos++;
    }
    else if (isBr(ch))
      break;
  }
  syntaxError();
}

function regexCharacterClass () {
  while (pos++ < end) {
    let ch = source.charCodeAt(pos);
    if (ch === 93/*]*/)
      return ch;
    if (ch === 92/*\*/)
      pos++;
    else if (ch === 10/*\n*/ || ch === 13/*\r*/)
      break;
  }
  syntaxError();
}

function regularExpression () {
  while (pos++ < end) {
    let ch = source.charCodeAt(pos);
    if (ch === 47/*/*/)
      return;
    if (ch === 91/*[*/)
      ch = regexCharacterClass();
    else if (ch === 92/*\*/)
      pos++;
    else if (ch === 10/*\n*/ || ch === 13/*\r*/)
      break;
  }
  syntaxError();
}

function readToWsOrPunctuator (ch) {
  do {
    if (isBrOrWs(ch) || isPunctuator(ch))
      return ch;
  } while (ch = source.charCodeAt(++pos));
  return ch;
}

// Note: non-asii BR and whitespace checks omitted for perf / footprint
// if there is a significant user need this can be reconsidered
function isBr (c) {
  return c === 13/*\r*/ || c === 10/*\n*/;
}

function isWsNotBr (c) {
  return c === 9 || c === 11 || c === 12 || c === 32 || c === 160;
}

function isBrOrWs (c) {
  return c > 8 && c < 14 || c === 32 || c === 160;
}

function isBrOrWsOrPunctuatorNotDot (c) {
  return c > 8 && c < 14 || c === 32 || c === 160 || isPunctuator(c) && c !== 46/*.*/;
}

function keywordStart (pos) {
  return pos === 0 || isBrOrWsOrPunctuatorNotDot(source.charCodeAt(pos - 1));
}

function readPrecedingKeyword (pos, match) {
  if (pos < match.length - 1)
    return false;
  return source.startsWith(match, pos - match.length + 1) && (pos === 0 || isBrOrWsOrPunctuatorNotDot(source.charCodeAt(pos - match.length)));
}

function readPrecedingKeyword1 (pos, ch) {
  return source.charCodeAt(pos) === ch && (pos === 0 || isBrOrWsOrPunctuatorNotDot(source.charCodeAt(pos - 1)));
}

// Detects one of case, debugger, delete, do, else, in, instanceof, new,
//   return, throw, typeof, void, yield, await
function isExpressionKeyword (pos) {
  switch (source.charCodeAt(pos)) {
    case 100/*d*/:
      switch (source.charCodeAt(pos - 1)) {
        case 105/*i*/:
          // void
          return readPrecedingKeyword(pos - 2, 'vo');
        case 108/*l*/:
          // yield
          return readPrecedingKeyword(pos - 2, 'yie');
        default:
          return false;
      }
    case 101/*e*/:
      switch (source.charCodeAt(pos - 1)) {
        case 115/*s*/:
          switch (source.charCodeAt(pos - 2)) {
            case 108/*l*/:
              // else
              return readPrecedingKeyword1(pos - 3, 101/*e*/);
            case 97/*a*/:
              // case
              return readPrecedingKeyword1(pos - 3, 99/*c*/);
            default:
              return false;
          }
        case 116/*t*/:
          // delete
          return readPrecedingKeyword(pos - 2, 'dele');
        default:
          return false;
      }
    case 102/*f*/:
      if (source.charCodeAt(pos - 1) !== 111/*o*/ || source.charCodeAt(pos - 2) !== 101/*e*/)
        return false;
      switch (source.charCodeAt(pos - 3)) {
        case 99/*c*/:
          // instanceof
          return readPrecedingKeyword(pos - 4, 'instan');
        case 112/*p*/:
          // typeof
          return readPrecedingKeyword(pos - 4, 'ty');
        default:
          return false;
      }
    case 110/*n*/:
      // in, return
      return readPrecedingKeyword1(pos - 1, 105/*i*/) || readPrecedingKeyword(pos - 1, 'retur');
    case 111/*o*/:
      // do
      return readPrecedingKeyword1(pos - 1, 100/*d*/);
    case 114/*r*/:
      // debugger
      return readPrecedingKeyword(pos - 1, 'debugge');
    case 116/*t*/:
      // await
      return readPrecedingKeyword(pos - 1, 'awai');
    case 119/*w*/:
      switch (source.charCodeAt(pos - 1)) {
        case 101/*e*/:
          // new
          return readPrecedingKeyword1(pos - 2, 110/*n*/);
        case 111/*o*/:
          // throw
          return readPrecedingKeyword(pos - 2, 'thr');
        default:
          return false;
      }
  }
  return false;
}

function isParenKeyword (curPos) {
  return source.charCodeAt(curPos) === 101/*e*/ && source.startsWith('whil', curPos - 4) ||
      source.charCodeAt(curPos) === 114/*r*/ && source.startsWith('fo', curPos - 2) ||
      source.charCodeAt(curPos - 1) === 105/*i*/ && source.charCodeAt(curPos) === 102/*f*/;
}

function isPunctuator (ch) {
  // 23 possible punctuator endings: !%&()*+,-./:;<=>?[]^{}|~
  return ch === 33/*!*/ || ch === 37/*%*/ || ch === 38/*&*/ ||
    ch > 39 && ch < 48 || ch > 57 && ch < 64 ||
    ch === 91/*[*/ || ch === 93/*]*/ || ch === 94/*^*/ ||
    ch > 122 && ch < 127;
}

function isExpressionPunctuator (ch) {
  // 20 possible expression endings: !%&(*+,-.:;<=>?[^{|~
  return ch === 33/*!*/ || ch === 37/*%*/ || ch === 38/*&*/ ||
    ch > 39 && ch < 47 && ch !== 41 || ch > 57 && ch < 64 ||
    ch === 91/*[*/ || ch === 94/*^*/ || ch > 122 && ch < 127 && ch !== 125/*}*/;
}

function isExpressionTerminator (curPos) {
  // detects:
  // => ; ) finally catch else
  // as all of these followed by a { will indicate a statement brace
  switch (source.charCodeAt(curPos)) {
    case 62/*>*/:
      return source.charCodeAt(curPos - 1) === 61/*=*/;
    case 59/*;*/:
    case 41/*)*/:
      return true;
    case 104/*h*/:
      return source.startsWith('catc', curPos - 4);
    case 121/*y*/:
      return source.startsWith('finall', curPos - 6);
    case 101/*e*/:
      return source.startsWith('els', curPos - 3);
  }
  return false;
}

function syntaxError () {
  throw Object.assign(new Error(`Parse error ${name}:${source.slice(0, pos).split('\n').length}:${pos - source.lastIndexOf('\n', pos - 1)}`), { idx: pos });
}

license: mit


es-module-lexer

description: Lexes ES modules returning their import/export metadata

build: npm install -g chomp ; chomp build

test: npm install -g chomp ; chomp test

author: Guy Bedford

license: MIT

files: dist types lexer.js

type: module

type: git

lib: lib

test: test


code:
export declare enum ImportType {
    /**
     * A normal static using any syntax variations
     *   import .. from 'module'
     */
    Static = 1,
    /**
     * A dynamic import expression `import(specifier)`
     * or `import(specifier, opts)`
     */
    Dynamic = 2,
    /**
     * An import.meta expression
     */
    ImportMeta = 3,
    /**
     * A source phase import
     *   import source x from 'module'
     */
    StaticSourcePhase = 4,
    /**
     * A dynamic source phase import
     *   import.source('module')
     */
    DynamicSourcePhase = 5
}
export interface ImportSpecifier {
    /**
     * Module name
     *
     * To handle escape sequences in specifier strings, the .n field of imported specifiers will be provided where possible.
     *
     * For dynamic import expressions, this field will be empty if not a valid JS string.
     *
     * @example
     * const [imports1, exports1] = parse(String.raw`import './\u0061\u0062.js'`);
     * imports1[0].n;
     * // Returns "./ab.js"
     *
     * const [imports2, exports2] = parse(`import("./ab.js")`);
     * imports2[0].n;
     * // Returns "./ab.js"
     *
     * const [imports3, exports3] = parse(`import("./" + "ab.js")`);
     * imports3[0].n;
     * // Returns undefined
     */
    readonly n: string | undefined;
    /**
     * Type of import statement
     */
    readonly t: ImportType;
    /**
     * Start of module specifier
     *
     * @example
     * const source = `import { a } from 'asdf'`;
     * const [imports, exports] = parse(source);
     * source.substring(imports[0].s, imports[0].e);
     * // Returns "asdf"
     */
    readonly s: number;
    /**
     * End of module specifier
     */
    readonly e: number;
    /**
     * Start of import statement
     *
     * @example
     * const source = `import { a } from 'asdf'`;
     * const [imports, exports] = parse(source);
     * source.substring(imports[0].ss, imports[0].se);
     * // Returns "import { a } from 'asdf';"
     */
    readonly ss: number;
    /**
     * End of import statement
     */
    readonly se: number;
    /**
     * If this import keyword is a dynamic import, this is the start value.
     * If this import keyword is a static import, this is -1.
     * If this import keyword is an import.meta expresion, this is -2.
     */
    readonly d: number;
    /**
     * If this import has an import assertion, this is the start value.
     * Otherwise this is `-1`.
     */
    readonly a: number;
}
export interface ExportSpecifier {
    /**
     * Exported name
     *
     * @example
     * const source = `export default []`;
     * const [imports, exports] = parse(source);
     * exports[0].n;
     * // Returns "default"
     *
     * @example
     * const source = `export const asdf = 42`;
     * const [imports, exports] = parse(source);
     * exports[0].n;
     * // Returns "asdf"
     */
    readonly n: string;
    /**
     * Local name, or undefined.
     *
     * @example
     * const source = `export default []`;
     * const [imports, exports] = parse(source);
     * exports[0].ln;
     * // Returns undefined
     *
     * @example
     * const asdf = 42;
     * const source = `export { asdf as a }`;
     * const [imports, exports] = parse(source);
     * exports[0].ln;
     * // Returns "asdf"
     */
    readonly ln: string | undefined;
    /**
     * Start of exported name
     *
     * @example
     * const source = `export default []`;
     * const [imports, exports] = parse(source);
     * source.substring(exports[0].s, exports[0].e);
     * // Returns "default"
     *
     * @example
     * const source = `export { 42 as asdf }`;
     * const [imports, exports] = parse(source);
     * source.substring(exports[0].s, exports[0].e);
     * // Returns "asdf"
     */
    readonly s: number;
    /**
     * End of exported name
     */
    readonly e: number;
    /**
     * Start of local name, or -1.
     *
     * @example
     * const asdf = 42;
     * const source = `export { asdf as a }`;
     * const [imports, exports] = parse(source);
     * source.substring(exports[0].ls, exports[0].le);
     * // Returns "asdf"
     */
    readonly ls: number;
    /**
     * End of local name, or -1.
     */
    readonly le: number;
}
export interface ParseError extends Error {
    idx: number;
}
/**
 * Outputs the list of exports and locations of import specifiers,
 * including dynamic import and import meta handling.
 *
 * @param source Source code to parser
 * @param name Optional sourcename
 * @returns Tuple contaning imports list and exports list.
 */
export declare function parse(source: string, name?: string): readonly [
    imports: ReadonlyArray<ImportSpecifier>,
    exports: ReadonlyArray<ExportSpecifier>,
    facade: boolean,
    hasModuleSyntax: boolean
];
/**
 * Wait for init to resolve before calling `parse`.
 */
export declare const init: Promise<void>;


code:
const { dirname, resolve } = require('path');
const { readdir, stat } = require('fs');
const { promisify } = require('util');

const toStats = promisify(stat);
const toRead = promisify(readdir);

module.exports = async function (start, callback) {
	let dir = resolve('.', start);
	let tmp, stats = await toStats(dir);

	if (!stats.isDirectory()) {
		dir = dirname(dir);
	}

	while (true) {
		tmp = await callback(dir, await toRead(dir));
		if (tmp) return resolve(dir, tmp);
		dir = dirname(tmp = dir);
		if (tmp === dir) break;
	}
}


code:
import { dirname, resolve } from 'path';
import { readdir, stat } from 'fs';
import { promisify } from 'util';

const toStats = promisify(stat);
const toRead = promisify(readdir);

export default async function (start, callback) {
	let dir = resolve('.', start);
	let tmp, stats = await toStats(dir);

	if (!stats.isDirectory()) {
		dir = dirname(dir);
	}

	while (true) {
		tmp = await callback(dir, await toRead(dir));
		if (tmp) return resolve(dir, tmp);
		dir = dirname(tmp = dir);
		if (tmp === dir) break;
	}
}


code:
type Promisable<T> = T | Promise<T>;

export type Callback = (
	directory: string,
	files: string[],
) => Promisable<string | false | void>;

export default function (
	directory: string,
	callback: Callback,
): Promise<string | void>;


code:
type Promisable<T> = T | Promise<T>;

declare namespace escalade {
	export type Callback = (
		directory: string,
		files: string[],
	) => Promisable<string | false | void>;
}

declare function escalade(
	directory: string,
	callback: escalade.Callback,
): Promise<string | void>;

export = escalade;


escalade

repository: lukeed/escalade

description: A tiny (183B to 210B) and fast utility to ascend parent directories

build: bundt

pretest: npm run build

test: uvu -r esm test -i fixtures

keywords: find parent parents directory search walk


code:
# escalade [![CI](https://github.com/lukeed/escalade/workflows/CI/badge.svg)](https://github.com/lukeed/escalade/actions) [![licenses](https://licenses.dev/b/npm/escalade)](https://licenses.dev/npm/escalade) [![codecov](https://badgen.now.sh/codecov/c/github/lukeed/escalade)](https://codecov.io/gh/lukeed/escalade)

> A tiny (183B to 210B) and [fast](#benchmarks) utility to ascend parent directories

With [escalade](https://en.wikipedia.org/wiki/Escalade), you can scale parent directories until you've found what you're looking for.
Given an input file or directory, `escalade` will continue executing your callback function until either:

1) the callback returns a truthy value
2) `escalade` has reached the system root directory (eg, `/`)

> **Important:**
Please note that `escalade` only deals with direct ancestry – it will not dive into parents' sibling directories.

---

**Notice:** As of v3.1.0, `escalade` now includes [Deno support](http://deno.land/x/escalade)! Please see [Deno Usage](#deno) below.

---

## Install

```
$ npm install --save escalade
```

## Modes

There are two "versions" of `escalade` available:

#### "async"
> **Node.js:** >= 8.x

> **Size (gzip):** 210 bytes

> **Availability:** [CommonJS](https://unpkg.com/escalade/dist/index.js), [ES Module](https://unpkg.com/escalade/dist/index.mjs)

This is the primary/default mode. It makes use of `async`/`await` and [`util.promisify`](https://nodejs.org/api/util.html#util_util_promisify_original).

#### "sync"
> **Node.js:** >= 6.x

> **Size (gzip):** 183 bytes

> **Availability:** [CommonJS](https://unpkg.com/escalade/sync/index.js), [ES Module](https://unpkg.com/escalade/sync/index.mjs)

This is the opt-in mode, ideal for scenarios where `async` usage cannot be supported.

## Usage

***Example Structure***

```
/Users/lukeed
  └── oss
    ├── license
    └── escalade
      ├── package.json
      └── test
        └── fixtures
          ├── index.js
          └── foobar
            └── demo.js
```

***Example Usage***

```js
//~> demo.js
import { join } from 'path';
import escalade from 'escalade';

const input = join(__dirname, 'demo.js');
// or: const input = __dirname;

const pkg = await escalade(input, (dir, names) => {
  console.log('~> dir:', dir);
  console.log('~> names:', names);
  console.log('---');

  if (names.includes('package.json')) {
    // will be resolved into absolute
    return 'package.json';
  }
});

//~> dir: /Users/lukeed/oss/escalade/test/fixtures/foobar
//~> names: ['demo.js']
//---
//~> dir: /Users/lukeed/oss/escalade/test/fixtures
//~> names: ['index.js', 'foobar']
//---
//~> dir: /Users/lukeed/oss/escalade/test
//~> names: ['fixtures']
//---
//~> dir: /Users/lukeed/oss/escalade
//~> names: ['package.json', 'test']
//---

console.log(pkg);
//=> /Users/lukeed/oss/escalade/package.json

// Now search for "missing123.txt"
// (Assume it doesn't exist anywhere!)
const missing = await escalade(input, (dir, names) => {
  console.log('~> dir:', dir);
  return names.includes('missing123.txt') && 'missing123.txt';
});

//~> dir: /Users/lukeed/oss/escalade/test/fixtures/foobar
//~> dir: /Users/lukeed/oss/escalade/test/fixtures
//~> dir: /Users/lukeed/oss/escalade/test
//~> dir: /Users/lukeed/oss/escalade
//~> dir: /Users/lukeed/oss
//~> dir: /Users/lukeed
//~> dir: /Users
//~> dir: /

console.log(missing);
//=> undefined
```

> **Note:** To run the above example with "sync" mode, import from `escalade/sync` and remove the `await` keyword.

## API

### escalade(input, callback)
Returns: `string|void` or `Promise<string|void>`

When your `callback` locates a file, `escalade` will resolve/return with an absolute path.

If your `callback` was never satisfied, then `escalade` will resolve/return with nothing (undefined).

> **Important:**
The `sync` and `async` versions share the same API.
The **only** difference is that `sync` is not Promise-based.

#### input
Type: `string`

The path from which to start ascending.

This may be a file or a directory path.
However, when `input` is a file, `escalade` will begin with its parent directory.

> **Important:** Unless given an absolute path, `input` will be resolved from `process.cwd()` location.

#### callback
Type: `Function`

The callback to execute for each ancestry level. It always is given two arguments:

1) `dir` - an absolute path of the current parent directory
2) `names` - a list (`string[]`) of contents _relative to_ the `dir` parent

> **Note:** The `names` list can contain names of files _and_ directories.

When your callback returns a _falsey_ value, then `escalade` will continue with `dir`'s parent directory, re-invoking your callback with new argument values.

When your callback returns a string, then `escalade` stops iteration immediately.

If the string is an absolute path, then it's left as is. Otherwise, the string is resolved into an absolute path _from_ the `dir` that housed the satisfying condition.

> **Important:** Your `callback` can be a `Promise/AsyncFunction` when using the "async" version of `escalade`.

## Benchmarks

> Running on Node.js v10.13.0

```
# Load Time
  find-up         3.891ms
  escalade        0.485ms
  escalade/sync   0.309ms

# Levels: 6 (target = "foo.txt"):
  find-up          x 24,856 ops/sec ±6.46% (55 runs sampled)
  escalade         x 73,084 ops/sec ±4.23% (73 runs sampled)
  find-up.sync     x  3,663 ops/sec ±1.12% (83 runs sampled)
  escalade/sync    x  9,360 ops/sec ±0.62% (88 runs sampled)

# Levels: 12 (target = "package.json"):
  find-up          x 29,300 ops/sec ±10.68% (70 runs sampled)
  escalade         x 73,685 ops/sec ± 5.66% (66 runs sampled)
  find-up.sync     x  1,707 ops/sec ± 0.58% (91 runs sampled)
  escalade/sync    x  4,667 ops/sec ± 0.68% (94 runs sampled)

# Levels: 18 (target = "missing123.txt"):
  find-up          x 21,818 ops/sec ±17.37% (14 runs sampled)
  escalade         x 67,101 ops/sec ±21.60% (20 runs sampled)
  find-up.sync     x  1,037 ops/sec ± 2.86% (88 runs sampled)
  escalade/sync    x  1,248 ops/sec ± 0.50% (93 runs sampled)
```

## Deno

As of v3.1.0, `escalade` is available on the Deno registry.

Please note that the [API](#api) is identical and that there are still [two modes](#modes) from which to choose:

```ts
// Choose "async" mode
import escalade from 'https://deno.land/escalade/async.ts';

// Choose "sync" mode
import escalade from 'https://deno.land/escalade/sync.ts';
```

> **Important:** The `allow-read` permission is required!

## Related

- [premove](https://github.com/lukeed/premove) - A tiny (247B) utility to remove items recursively
- [totalist](https://github.com/lukeed/totalist) - A tiny (195B to 224B) utility to recursively list all (total) files in a directory
- [mk-dirs](https://github.com/lukeed/mk-dirs) - A tiny (420B) utility to make a directory and its parents, recursively

## License

MIT © [Luke Edwards](https://lukeed.com)


code:
export type Callback = (
	directory: string,
	files: string[],
) => string | false | void;

export default function (
	directory: string,
	callback: Callback,
): string | void;


code:
declare namespace escalade {
	export type Callback = (
		directory: string,
		files: string[],
	) => string | false | void;
}

declare function escalade(
	directory: string,
	callback: escalade.Callback,
): string | void;

export = escalade;


code:
const { dirname, resolve } = require('path');
const { readdirSync, statSync } = require('fs');

module.exports = function (start, callback) {
	let dir = resolve('.', start);
	let tmp, stats = statSync(dir);

	if (!stats.isDirectory()) {
		dir = dirname(dir);
	}

	while (true) {
		tmp = callback(dir, readdirSync(dir));
		if (tmp) return resolve(dir, tmp);
		dir = dirname(tmp = dir);
		if (tmp === dir) break;
	}
}


code:
import { dirname, resolve } from 'path';
import { readdirSync, statSync } from 'fs';

export default function (start, callback) {
	let dir = resolve('.', start);
	let tmp, stats = statSync(dir);

	if (!stats.isDirectory()) {
		dir = dirname(dir);
	}

	while (true) {
		tmp = callback(dir, readdirSync(dir));
		if (tmp) return resolve(dir, tmp);
		dir = dirname(tmp = dir);
		if (tmp === dir) break;
	}
}


code:
# escape-html

  Escape string for use in HTML

## Example

```js
var escape = require('escape-html');
var html = escape('foo & bar');
// -> foo & bar
```

## Benchmark

```
$ npm run-script bench

> escape-html@1.0.3 bench nodejs-escape-html
> node benchmark/index.js

  http_parser@1.0
  node@0.10.33
  v8@3.14.5.9
  ares@1.9.0-DEV
  uv@0.10.29
  zlib@1.2.3
  modules@11
  openssl@1.0.1j

  1 test completed.
  2 tests completed.
  3 tests completed.

  no special characters    x 19,435,271 ops/sec ±0.85% (187 runs sampled)
  single special character x  6,132,421 ops/sec ±0.67% (194 runs sampled)
  many special characters  x  3,175,826 ops/sec ±0.65% (193 runs sampled)
```

## License

  MIT


code:
/*!
 * escape-html
 * Copyright(c) 2012-2013 TJ Holowaychuk
 * Copyright(c) 2015 Andreas Lubbe
 * Copyright(c) 2015 Tiancheng "Timothy" Gu
 * MIT Licensed
 */

'use strict';

/**
 * Module variables.
 * @private
 */

var matchHtmlRegExp = /["'&<>]/;

/**
 * Module exports.
 * @public
 */

module.exports = escapeHtml;

/**
 * Escape special characters in the given string of html.
 *
 * @param  {string} string The string to escape for inserting into HTML
 * @return {string}
 * @public
 */

function escapeHtml(string) {
  var str = '' + string;
  var match = matchHtmlRegExp.exec(str);

  if (!match) {
    return str;
  }

  var escape;
  var html = '';
  var index = 0;
  var lastIndex = 0;

  for (index = match.index; index < str.length; index++) {
    switch (str.charCodeAt(index)) {
      case 34: // "
        escape = '"';
        break;
      case 38: // &
        escape = '&';
        break;
      case 39: // '
        escape = ''';
        break;
      case 60: // <
        escape = '<';
        break;
      case 62: // >
        escape = '>';
        break;
      default:
        continue;
    }

    if (lastIndex !== index) {
      html += str.substring(lastIndex, index);
    }

    lastIndex = index + 1;
    html += escape;
  }

  return lastIndex !== index
    ? html + str.substring(lastIndex, index)
    : html;
}


escape-html

description: Escape string for use in HTML

keywords: escape html utility

repository: component/escape-html

bench: node benchmark/index.js


code:
'use strict';

var matchOperatorsRe = /[|\\{}()[\]^$+*?.]/g;

module.exports = function (str) {
	if (typeof str !== 'string') {
		throw new TypeError('Expected a string');
	}

	return str.replace(matchOperatorsRe, '\\$&');
};


escape-string-regexp

description: Escape RegExp special characters

repository: sindresorhus/escape-string-regexp

maintainers: Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)

maintainers: Joshua Boy Nicolai Appelman <joshua@jbna.nl> (jbna.nl)

test: xo && ava

keywords: escape regex regexp re regular expression string str special characters


code:
# escape-string-regexp [![Build Status](https://travis-ci.org/sindresorhus/escape-string-regexp.svg?branch=master)](https://travis-ci.org/sindresorhus/escape-string-regexp)

> Escape RegExp special characters

## Install

```
$ npm install --save escape-string-regexp
```

## Usage

```js
const escapeStringRegexp = require('escape-string-regexp');

const escapedString = escapeStringRegexp('how much $ for a unicorn?');
//=> 'how much \$ for a unicorn\?'

new RegExp(escapedString);
```

## License

MIT © [Sindre Sorhus](http://sindresorhus.com)


code:
throw new Error(
  '"prettier/@typescript-eslint" has been merged into "prettier" in eslint-config-prettier 8.0.0. See: https://github.com/prettier/eslint-config-prettier/blob/main/CHANGELOG.md#version-800-2021-02-21'
);


code:
# eslint-config-prettier

Turns off all rules that are unnecessary or might conflict with [Prettier].

This lets you use your favorite shareable config without letting its stylistic choices get in the way when using Prettier.

Note that this config _only_ turns rules _off,_ so it only makes sense using it together with some other config.

[prettier]: https://github.com/prettier/prettier

**[➡ Full readme](https://github.com/prettier/eslint-config-prettier/)**


code:
throw new Error(
  '"prettier/babel" has been merged into "prettier" in eslint-config-prettier 8.0.0. See: https://github.com/prettier/eslint-config-prettier/blob/main/CHANGELOG.md#version-800-2021-02-21'
);


code:
#!/usr/bin/env node

"use strict";

const validators = require("./validators");
const config = require("..");
const prettier = require("../prettier");

// Require locally installed eslint, for `npx eslint-config-prettier` support
// with no local eslint-config-prettier installation.
const localRequire = (request) =>
  require(
    require.resolve(request, {
      paths: [process.cwd(), ...require.resolve.paths("eslint")],
    })
  );

let experimentalApi = {};
try {
  experimentalApi = localRequire("eslint/use-at-your-own-risk");
  // eslint-disable-next-line unicorn/prefer-optional-catch-binding
} catch (_error) {}

const { ESLint, FlatESLint = experimentalApi.FlatESLint } =
  localRequire("eslint");

const SPECIAL_RULES_URL =
  "https://github.com/prettier/eslint-config-prettier#special-rules";

const PRETTIER_RULES_URL =
  "https://github.com/prettier/eslint-config-prettier#arrow-body-style-and-prefer-arrow-callback";

if (module === require.main) {
  const args = process.argv.slice(2);

  if (args.length === 0) {
    console.error(help());
    process.exit(1);
  }

  const eslint = new ESLint();
  const flatESLint = FlatESLint === undefined ? undefined : new FlatESLint();

  Promise.all(
    args.map((file) => {
      switch (process.env.ESLINT_USE_FLAT_CONFIG) {
        case "true": {
          return flatESLint.calculateConfigForFile(file);
        }
        case "false": {
          return eslint.calculateConfigForFile(file);
        }
        default: {
          // This turns synchronous errors (such as `.calculateConfigForFile` not existing)
          // and turns them into promise rejections.
          return Promise.resolve()
            .then(() => flatESLint.calculateConfigForFile(file))
            .catch(() => eslint.calculateConfigForFile(file));
        }
      }
    })
  )
    .then((configs) => {
      const rules = configs.flatMap(({ rules }, index) =>
        Object.entries(rules).map((entry) => [...entry, args[index]])
      );
      const result = processRules(rules);
      if (result.stderr) {
        console.error(result.stderr);
      }
      if (result.stdout) {
        console.error(result.stdout);
      }
      process.exit(result.code);
    })
    .catch((error) => {
      console.error(error.message);
      process.exit(1);
    });
}

function help() {
  return `
Usage: npx eslint-config-prettier FILE...

Resolves an ESLint configuration for every given FILE and checks if they
contain rules that are unnecessary or conflict with Prettier. Example:

  npx eslint-config-prettier index.js test/index.js other/file/to/check.js

Exit codes:

0: No automatically detectable problems found.
1: General error.
2: Conflicting rules found.

For more information, see:
https://github.com/prettier/eslint-config-prettier#cli-helper-tool
  `.trim();
}

function processRules(configRules) {
  const regularRules = filterRules(config.rules, (_, value) => value === "off");
  const optionsRules = filterRules(
    config.rules,
    (ruleName, value) => value === 0 && ruleName in validators
  );
  const specialRules = filterRules(
    config.rules,
    (ruleName, value) => value === 0 && !(ruleName in validators)
  );

  const enabledRules = configRules
    .map(([ruleName, value, source]) => {
      const arrayValue = Array.isArray(value) ? value : [value];
      const [level, ...options] = arrayValue;
      const isOff = level === "off" || level === 0;
      return isOff ? null : { ruleName, options, source };
    })
    .filter(Boolean);

  const flaggedRules = enabledRules.filter(
    ({ ruleName }) => ruleName in config.rules
  );

  const regularFlaggedRuleNames = filterRuleNames(
    flaggedRules,
    ({ ruleName }) => ruleName in regularRules
  );
  const optionsFlaggedRuleNames = filterRuleNames(
    flaggedRules,
    ({ ruleName, ...rule }) =>
      ruleName in optionsRules && !validators[ruleName](rule)
  );
  const specialFlaggedRuleNames = filterRuleNames(
    flaggedRules,
    ({ ruleName }) => ruleName in specialRules
  );
  const prettierFlaggedRuleNames = filterRuleNames(
    enabledRules,
    ({ ruleName, source }) =>
      ruleName in prettier.rules &&
      enabledRules.some(
        (rule) =>
          rule.ruleName === "prettier/prettier" && rule.source === source
      )
  );

  const regularMessage = [
    "The following rules are unnecessary or might conflict with Prettier:",
    "",
    printRuleNames(regularFlaggedRuleNames),
  ].join("\n");

  const optionsMessage = [
    "The following rules are enabled with config that might conflict with Prettier. See:",
    SPECIAL_RULES_URL,
    "",
    printRuleNames(optionsFlaggedRuleNames),
  ].join("\n");

  const specialMessage = [
    "The following rules are enabled but cannot be automatically checked. See:",
    SPECIAL_RULES_URL,
    "",
    printRuleNames(specialFlaggedRuleNames),
  ].join("\n");

  const prettierMessage = [
    "The following rules can cause issues when using eslint-plugin-prettier at the same time.",
    "Only enable them if you know what you are doing! See:",
    PRETTIER_RULES_URL,
    "",
    printRuleNames(prettierFlaggedRuleNames),
  ].join("\n");

  if (
    regularFlaggedRuleNames.length === 0 &&
    optionsFlaggedRuleNames.length === 0
  ) {
    const message =
      specialFlaggedRuleNames.length === 0 &&
      prettierFlaggedRuleNames.length === 0
        ? "No rules that are unnecessary or conflict with Prettier were found."
        : [
            specialFlaggedRuleNames.length === 0 ? null : specialMessage,
            prettierFlaggedRuleNames.length === 0 ? null : prettierMessage,
            "Other than that, no rules that are unnecessary or conflict with Prettier were found.",
          ]
            .filter(Boolean)
            .join("\n\n");

    return {
      stdout: message,
      code: 0,
    };
  }

  const message = [
    regularFlaggedRuleNames.length === 0 ? null : regularMessage,
    optionsFlaggedRuleNames.length === 0 ? null : optionsMessage,
    specialFlaggedRuleNames.length === 0 ? null : specialMessage,
    prettierFlaggedRuleNames.length === 0 ? null : prettierMessage,
  ]
    .filter(Boolean)
    .join("\n\n");

  return {
    stdout: message,
    code: 2,
  };
}

function filterRules(rules, fn) {
  return Object.fromEntries(
    Object.entries(rules)
      .filter(([ruleName, value]) => fn(ruleName, value))
      .map(([ruleName]) => [ruleName, true])
  );
}

function filterRuleNames(rules, fn) {
  return [
    ...new Set(rules.filter((rule) => fn(rule)).map((rule) => rule.ruleName)),
  ];
}

function printRuleNames(ruleNames) {
  return ruleNames
    .slice()
    .sort()
    .map((ruleName) => `- ${ruleName}`)
    .join("\n");
}

exports.processRules = processRules;


code:
"use strict";

// These validator functions answer the question “Is the config valid?” – return
// `false` if the options DO conflict with Prettier, and `true` if they don’t.

module.exports = {
  "curly"({ options }) {
    if (options.length === 0) {
      return true;
    }

    const firstOption = options[0];
    return firstOption !== "multi-line" && firstOption !== "multi-or-nest";
  },

  "lines-around-comment"({ options }) {
    if (options.length === 0) {
      return false;
    }

    const firstOption = options[0];
    return Boolean(
      firstOption &&
        firstOption.allowBlockStart &&
        firstOption.allowBlockEnd &&
        firstOption.allowObjectStart &&
        firstOption.allowObjectEnd &&
        firstOption.allowArrayStart &&
        firstOption.allowArrayEnd
    );
  },

  "no-confusing-arrow"({ options }) {
    if (options.length === 0) {
      return false;
    }

    const firstOption = options[0];
    return firstOption ? firstOption.allowParens === false : false;
  },

  "no-tabs"({ options }) {
    if (options.length === 0) {
      return false;
    }

    const firstOption = options[0];
    return Boolean(firstOption && firstOption.allowIndentationTabs);
  },

  "unicorn/template-indent"({ options }) {
    if (options.length === 0) {
      return false;
    }

    const { comments = [], tags = [] } = options[0] || {};

    return (
      Array.isArray(comments) &&
      Array.isArray(tags) &&
      !(
        comments.includes("GraphQL") ||
        comments.includes("HTML") ||
        tags.includes("css") ||
        tags.includes("graphql") ||
        tags.includes("gql") ||
        tags.includes("html") ||
        tags.includes("markdown") ||
        tags.includes("md")
      )
    );
  },

  "vue/html-self-closing"({ options }) {
    if (options.length === 0) {
      return false;
    }

    const firstOption = options[0];
    return Boolean(
      firstOption && firstOption.html && firstOption.html.void === "any"
      // Enable when Prettier supports SVG: https://github.com/prettier/prettier/issues/5322
      // && firstOption.svg === "any"
    );
  },
};


code:
throw new Error(
  '"prettier/flowtype" has been merged into "prettier" in eslint-config-prettier 8.0.0. See: https://github.com/prettier/eslint-config-prettier/blob/main/CHANGELOG.md#version-800-2021-02-21'
);


code:
"use strict";

const includeDeprecated = !process.env.ESLINT_CONFIG_PRETTIER_NO_DEPRECATED;

const specialRule = 0;

module.exports = {
  rules: {
    // The following rules can be used in some cases. See the README for more
    // information. These are marked with `0` instead of `"off"` so that a
    // script can distinguish them. Note that there are a few more of these
    // in the deprecated section below.
    "curly": specialRule,
    "no-unexpected-multiline": specialRule,
    "@typescript-eslint/lines-around-comment": specialRule,
    "@typescript-eslint/quotes": specialRule,
    "babel/quotes": specialRule,
    "unicorn/template-indent": specialRule,
    "vue/html-self-closing": specialRule,
    "vue/max-len": specialRule,

    // The rest are rules that you never need to enable when using Prettier.
    "@babel/object-curly-spacing": "off",
    "@babel/semi": "off",
    "@typescript-eslint/block-spacing": "off",
    "@typescript-eslint/brace-style": "off",
    "@typescript-eslint/comma-dangle": "off",
    "@typescript-eslint/comma-spacing": "off",
    "@typescript-eslint/func-call-spacing": "off",
    "@typescript-eslint/indent": "off",
    "@typescript-eslint/key-spacing": "off",
    "@typescript-eslint/keyword-spacing": "off",
    "@typescript-eslint/member-delimiter-style": "off",
    "@typescript-eslint/no-extra-parens": "off",
    "@typescript-eslint/no-extra-semi": "off",
    "@typescript-eslint/object-curly-spacing": "off",
    "@typescript-eslint/semi": "off",
    "@typescript-eslint/space-before-blocks": "off",
    "@typescript-eslint/space-before-function-paren": "off",
    "@typescript-eslint/space-infix-ops": "off",
    "@typescript-eslint/type-annotation-spacing": "off",
    "babel/object-curly-spacing": "off",
    "babel/semi": "off",
    "flowtype/boolean-style": "off",
    "flowtype/delimiter-dangle": "off",
    "flowtype/generic-spacing": "off",
    "flowtype/object-type-curly-spacing": "off",
    "flowtype/object-type-delimiter": "off",
    "flowtype/quotes": "off",
    "flowtype/semi": "off",
    "flowtype/space-after-type-colon": "off",
    "flowtype/space-before-generic-bracket": "off",
    "flowtype/space-before-type-colon": "off",
    "flowtype/union-intersection-spacing": "off",
    "react/jsx-child-element-spacing": "off",
    "react/jsx-closing-bracket-location": "off",
    "react/jsx-closing-tag-location": "off",
    "react/jsx-curly-newline": "off",
    "react/jsx-curly-spacing": "off",
    "react/jsx-equals-spacing": "off",
    "react/jsx-first-prop-new-line": "off",
    "react/jsx-indent": "off",
    "react/jsx-indent-props": "off",
    "react/jsx-max-props-per-line": "off",
    "react/jsx-newline": "off",
    "react/jsx-one-expression-per-line": "off",
    "react/jsx-props-no-multi-spaces": "off",
    "react/jsx-tag-spacing": "off",
    "react/jsx-wrap-multilines": "off",
    "standard/array-bracket-even-spacing": "off",
    "standard/computed-property-even-spacing": "off",
    "standard/object-curly-even-spacing": "off",
    "unicorn/empty-brace-spaces": "off",
    "unicorn/no-nested-ternary": "off",
    "unicorn/number-literal-case": "off",
    "vue/array-bracket-newline": "off",
    "vue/array-bracket-spacing": "off",
    "vue/array-element-newline": "off",
    "vue/arrow-spacing": "off",
    "vue/block-spacing": "off",
    "vue/block-tag-newline": "off",
    "vue/brace-style": "off",
    "vue/comma-dangle": "off",
    "vue/comma-spacing": "off",
    "vue/comma-style": "off",
    "vue/dot-location": "off",
    "vue/func-call-spacing": "off",
    "vue/html-closing-bracket-newline": "off",
    "vue/html-closing-bracket-spacing": "off",
    "vue/html-end-tags": "off",
    "vue/html-indent": "off",
    "vue/html-quotes": "off",
    "vue/key-spacing": "off",
    "vue/keyword-spacing": "off",
    "vue/max-attributes-per-line": "off",
    "vue/multiline-html-element-content-newline": "off",
    "vue/multiline-ternary": "off",
    "vue/mustache-interpolation-spacing": "off",
    "vue/no-extra-parens": "off",
    "vue/no-multi-spaces": "off",
    "vue/no-spaces-around-equal-signs-in-attribute": "off",
    "vue/object-curly-newline": "off",
    "vue/object-curly-spacing": "off",
    "vue/object-property-newline": "off",
    "vue/operator-linebreak": "off",
    "vue/quote-props": "off",
    "vue/script-indent": "off",
    "vue/singleline-html-element-content-newline": "off",
    "vue/space-in-parens": "off",
    "vue/space-infix-ops": "off",
    "vue/space-unary-ops": "off",
    "vue/template-curly-spacing": "off",

    ...(includeDeprecated && {
      // Removed in version 0.10.0.
      // https://eslint.org/docs/latest/rules/space-unary-word-ops
      "space-unary-word-ops": "off",

      // Removed in version 1.0.0.
      // https://github.com/eslint/eslint/issues/1898
      "generator-star": "off",
      "no-comma-dangle": "off",
      "no-reserved-keys": "off",
      "no-space-before-semi": "off",
      "no-wrap-func": "off",
      "space-after-function-name": "off",
      "space-before-function-parentheses": "off",
      "space-in-brackets": "off",

      // Removed in version 2.0.0.
      // https://github.com/eslint/eslint/issues/5032
      "no-arrow-condition": "off",
      "space-after-keywords": "off",
      "space-before-keywords": "off",
      "space-return-throw-case": "off",

      // Deprecated since version 3.3.0.
      // https://eslint.org/docs/rules/no-spaced-func
      "no-spaced-func": "off",

      // Deprecated since version 4.0.0.
      // https://github.com/eslint/eslint/pull/8286
      "indent-legacy": "off",

      // Deprecated since version 8.53.0.
      // https://eslint.org/blog/2023/10/deprecating-formatting-rules/
      "array-bracket-newline": "off",
      "array-bracket-spacing": "off",
      "array-element-newline": "off",
      "arrow-parens": "off",
      "arrow-spacing": "off",
      "block-spacing": "off",
      "brace-style": "off",
      "comma-dangle": "off",
      "comma-spacing": "off",
      "comma-style": "off",
      "computed-property-spacing": "off",
      "dot-location": "off",
      "eol-last": "off",
      "func-call-spacing": "off",
      "function-call-argument-newline": "off",
      "function-paren-newline": "off",
      "generator-star-spacing": "off",
      "implicit-arrow-linebreak": "off",
      "indent": "off",
      "jsx-quotes": "off",
      "key-spacing": "off",
      "keyword-spacing": "off",
      "linebreak-style": "off",
      "lines-around-comment": specialRule,
      "max-len": specialRule,
      "max-statements-per-line": "off",
      "multiline-ternary": "off",
      "new-parens": "off",
      "newline-per-chained-call": "off",
      "no-confusing-arrow": specialRule,
      "no-extra-parens": "off",
      "no-extra-semi": "off",
      "no-floating-decimal": "off",
      "no-mixed-operators": specialRule,
      "no-mixed-spaces-and-tabs": "off",
      "no-multi-spaces": "off",
      "no-multiple-empty-lines": "off",
      "no-tabs": specialRule,
      "no-trailing-spaces": "off",
      "no-whitespace-before-property": "off",
      "nonblock-statement-body-position": "off",
      "object-curly-newline": "off",
      "object-curly-spacing": "off",
      "object-property-newline": "off",
      "one-var-declaration-per-line": "off",
      "operator-linebreak": "off",
      "padded-blocks": "off",
      "quote-props": "off",
      "quotes": specialRule,
      "rest-spread-spacing": "off",
      "semi": "off",
      "semi-spacing": "off",
      "semi-style": "off",
      "space-before-blocks": "off",
      "space-before-function-paren": "off",
      "space-in-parens": "off",
      "space-infix-ops": "off",
      "space-unary-ops": "off",
      "switch-colon-spacing": "off",
      "template-curly-spacing": "off",
      "template-tag-spacing": "off",
      "wrap-iife": "off",
      "wrap-regex": "off",
      "yield-star-spacing": "off",

      // Deprecated since version 7.0.0.
      // https://github.com/yannickcr/eslint-plugin-react/blob/master/CHANGELOG.md#700---2017-05-06
      "react/jsx-space-before-closing": "off",
    }),
  },
};


eslint-config-prettier

author: Simon Lydell

description: Turns off all rules that are unnecessary or might conflict with Prettier.

repository: prettier/eslint-config-prettier

keywords: eslint eslintconfig prettier


code:
"use strict";

module.exports = {
  rules: {
    // These are safe to use as long as the `"prettier/prettier"` rule from
    // eslint-plugin-prettier isn’t enabled.
    // These are also included in `"plugin:prettier/recommended"`:
    // https://github.com/prettier/eslint-plugin-prettier#recommended-configuration
    "arrow-body-style": 0,
    "prefer-arrow-callback": 0,
  },
};


code:
throw new Error(
  '"prettier/react" has been merged into "prettier" in eslint-config-prettier 8.0.0. See: https://github.com/prettier/eslint-config-prettier/blob/main/CHANGELOG.md#version-800-2021-02-21'
);


code:
throw new Error(
  '"prettier/standard" has been merged into "prettier" in eslint-config-prettier 8.0.0. See: https://github.com/prettier/eslint-config-prettier/blob/main/CHANGELOG.md#version-800-2021-02-21'
);


code:
throw new Error(
  '"prettier/unicorn" has been merged into "prettier" in eslint-config-prettier 8.0.0. See: https://github.com/prettier/eslint-config-prettier/blob/main/CHANGELOG.md#version-800-2021-02-21'
);


code:
throw new Error(
  '"prettier/vue" has been merged into "prettier" in eslint-config-prettier 8.0.0. See: https://github.com/prettier/eslint-config-prettier/blob/main/CHANGELOG.md#version-800-2021-02-21'
);


code:
# eslint-plugin-prettier [![Build Status](https://github.com/prettier/eslint-plugin-prettier/workflows/CI/badge.svg?branch=master)](https://github.com/prettier/eslint-plugin-prettier/actions?query=workflow%3ACI+branch%3Amaster)

Runs [Prettier](https://github.com/prettier/prettier) as an [ESLint](https://eslint.org) rule and reports differences as individual ESLint issues.

If your desired formatting does not match Prettier’s output, you should use a different tool such as [prettier-eslint](https://github.com/prettier/prettier-eslint) instead.

Please read [Integrating with linters](https://prettier.io/docs/en/integrating-with-linters.html) before installing.

## TOC <!-- omit in toc -->

- [Sample](#sample)
- [Installation](#installation)
- [Configuration (legacy: `.eslintrc*`)](#configuration-legacy-eslintrc)
- [Configuration (new: `eslint.config.js`)](#configuration-new-eslintconfigjs)
- [`Svelte` support](#svelte-support)
- [`arrow-body-style` and `prefer-arrow-callback` issue](#arrow-body-style-and-prefer-arrow-callback-issue)
- [Options](#options)
- [Sponsors](#sponsors)
- [Backers](#backers)
- [Contributing](#contributing)
- [Changelog](#changelog)
- [License](#license)

## Sample

```js
error: Insert `,` (prettier/prettier) at pkg/commons-atom/ActiveEditorRegistry.js:22:25:
  20 | import {
  21 |   observeActiveEditorsDebounced,
> 22 |   editorChangesDebounced
     |                         ^
  23 | } from './debounced';;
  24 |
  25 | import {observableFromSubscribeFunction} from '../commons-node/event';

error: Delete `;` (prettier/prettier) at pkg/commons-atom/ActiveEditorRegistry.js:23:21:
  21 |   observeActiveEditorsDebounced,
  22 |   editorChangesDebounced
> 23 | } from './debounced';;
     |                     ^
  24 |
  25 | import {observableFromSubscribeFunction} from '../commons-node/event';
  26 | import {cacheWhileSubscribed} from '../commons-node/observable';

2 errors found.
```

> `./node_modules/.bin/eslint --format codeframe pkg/commons-atom/ActiveEditorRegistry.js` (code from [nuclide](https://github.com/facebook/nuclide)).

## Installation

```sh
npm install --save-dev eslint-plugin-prettier eslint-config-prettier
npm install --save-dev --save-exact prettier
```

**_`eslint-plugin-prettier` does not install Prettier or ESLint for you._** _You must install these yourself._

This plugin works best if you disable all other ESLint rules relating to code formatting, and only enable rules that detect potential bugs. If another active ESLint rule disagrees with `prettier` about how code should be formatted, it will be impossible to avoid lint errors. Our recommended configuration automatically enables [`eslint-config-prettier`](https://github.com/prettier/eslint-config-prettier) to disable all formatting-related ESLint rules.

## Configuration (legacy: `.eslintrc*`)

For [legacy configuration](https://eslint.org/docs/latest/use/configure/configuration-files), this plugin ships with a `plugin:prettier/recommended` config that sets up both `eslint-plugin-prettier` and [`eslint-config-prettier`](https://github.com/prettier/eslint-config-prettier) in one go.

Add `plugin:prettier/recommended` as the _last_ item in the extends array in your `.eslintrc*` config file, so that `eslint-config-prettier` has the opportunity to override other configs:

```json
{
  "extends": ["plugin:prettier/recommended"]
}
```

This will:

- Enable the `prettier/prettier` rule.
- Disable the `arrow-body-style` and `prefer-arrow-callback` rules which are problematic with this plugin - see the below for why.
- Enable the `eslint-config-prettier` config which will turn off ESLint rules that conflict with Prettier.

## Configuration (new: `eslint.config.js`)

For [flat configuration](https://eslint.org/docs/latest/use/configure/configuration-files-new), this plugin ships with an `eslint-plugin-prettier/recommended` config that sets up both `eslint-plugin-prettier` and [`eslint-config-prettier`](https://github.com/prettier/eslint-config-prettier) in one go.

Import `eslint-plugin-prettier/recommended` and add it as the _last_ item in the configuration array in your `eslint.config.js` file so that `eslint-config-prettier` has the opportunity to override other configs:

```js
const eslintPluginPrettierRecommended = require('eslint-plugin-prettier/recommended');

module.exports = [
  // Any other config imports go at the top
  eslintPluginPrettierRecommended,
];
```

This will:

- Enable the `prettier/prettier` rule.
- Disable the `arrow-body-style` and `prefer-arrow-callback` rules which are problematic with this plugin - see the below for why.
- Enable the `eslint-config-prettier` config which will turn off ESLint rules that conflict with Prettier.

## `Svelte` support

We recommend to use [`eslint-plugin-svelte`](https://github.com/ota-meshi/eslint-plugin-svelte) instead of [`eslint-plugin-svelte3`](https://github.com/sveltejs/eslint-plugin-svelte3) because `eslint-plugin-svelte` has a correct [`eslint-svelte-parser`](https://github.com/ota-meshi/svelte-eslint-parser) instead of hacking.

When use with `eslint-plugin-svelte3`, `eslint-plugin-prettier` will just ignore the text passed by `eslint-plugin-svelte3`, because the text has been modified.

If you still decide to use `eslint-plugin-svelte3`, you'll need to run `prettier --write *.svelte` manually.

## `arrow-body-style` and `prefer-arrow-callback` issue

If you use [arrow-body-style](https://eslint.org/docs/rules/arrow-body-style) or [prefer-arrow-callback](https://eslint.org/docs/rules/prefer-arrow-callback) together with the `prettier/prettier` rule from this plugin, you can in some cases end up with invalid code due to a bug in ESLint’s autofix – see [issue #65](https://github.com/prettier/eslint-plugin-prettier/issues/65).

For this reason, it’s recommended to turn off these rules. The `plugin:prettier/recommended` config does that for you.

You _can_ still use these rules together with this plugin if you want, because the bug does not occur _all the time._ But if you do, you need to keep in mind that you might end up with invalid code, where you manually have to insert a missing closing parenthesis to get going again.

If you’re fixing large of amounts of previously unformatted code, consider temporarily disabling the `prettier/prettier` rule and running `eslint --fix` and `prettier --write` separately.

## Options

> Note: While it is possible to pass options to Prettier via your ESLint configuration file, it is not recommended because editor extensions such as `prettier-atom` and `prettier-vscode` **will** read [`.prettierrc`](https://prettier.io/docs/en/configuration.html), but **won't** read settings from ESLint, which can lead to an inconsistent experience.

- The first option:

  - An object representing [options](https://prettier.io/docs/en/options.html) that will be passed into prettier. Example:

    ```json
    {
      "prettier/prettier": [
        "error",
        {
          "singleQuote": true,
          "parser": "flow"
        }
      ]
    }
    ```

    NB: This option will merge and override any config set with `.prettierrc` files

- The second option:

  - An object with the following options

    - `usePrettierrc`: Enables loading of the Prettier configuration file, (default: `true`). May be useful if you are using multiple tools that conflict with each other, or do not wish to mix your ESLint settings with your Prettier configuration. And also, it is possible to run prettier without loading the prettierrc config file [via the CLI's --no-config option](https://prettier.io/docs/en/cli.html#--no-config) or through the API by [calling prettier.format() without passing through the options generated by calling resolveConfig](https://prettier.io/docs/en/api.html#prettierresolveconfigfilepath--options).

      ```json
      {
        "prettier/prettier": [
          "error",
          {},
          {
            "usePrettierrc": false
          }
        ]
      }
      ```

    - `fileInfoOptions`: Options that are passed to [prettier.getFileInfo](https://prettier.io/docs/en/api.html#prettiergetfileinfofilepath--options) to decide whether a file needs to be formatted. Can be used for example to opt-out from ignoring files located in `node_modules` directories.

      ```json
      {
        "prettier/prettier": [
          "error",
          {},
          {
            "fileInfoOptions": {
              "withNodeModules": true
            }
          }
        ]
      }
      ```

- The rule is auto fixable -- if you run `eslint` with the `--fix` flag, your code will be formatted according to `prettier` style.

---

## Sponsors

| @prettier/plugin-eslint                                                                                | eslint-config-prettier                                                                                | eslint-plugin-prettier                                                                                | prettier-eslint                                                                                | prettier-eslint-cli                                                                                |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [![@prettier/plugin-eslint Open Collective sponsors](https://opencollective.com/prettier-plugin-eslint/tiers/sponsors.svg)](https://opencollective.com/prettier-plugin-eslint) | [![eslint-config-prettier Open Collective backers](https://opencollective.com/eslint-config-prettier/tiers/sponsors.svg)](https://opencollective.com/eslint-config-prettier) | [![eslint-plugin-prettier Open Collective backers](https://opencollective.com/eslint-plugin-prettier/tiers/sponsors.svg)](https://opencollective.com/rxts) | [![prettier-eslint Open Collective sponsors](https://opencollective.com/prettier-eslint/tiers/sponsors.svg)](https://opencollective.com/prettier-eslint) | [![prettier-eslint-cli Open Collective backers](https://opencollective.com/prettier-eslint-cli/tiers/sponsors.svg)](https://opencollective.com/prettier-eslint-cli) |

## Backers

| @prettier/plugin-eslint                                                                                | eslint-config-prettier                                                                                | eslint-plugin-prettier                                                                                | prettier-eslint                                                                                | prettier-eslint-cli                                                                                |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| [![@prettier/plugin-eslint Open Collective backers](https://opencollective.com/prettier-plugin-eslint/tiers/backers.svg)](https://opencollective.com/prettier-plugin-eslint) | [![eslint-config-prettier Open Collective backers](https://opencollective.com/eslint-config-prettier/tiers/backers.svg)](https://opencollective.com/eslint-config-prettier) | [![eslint-plugin-prettier Open Collective backers](https://opencollective.com/eslint-plugin-prettier/tiers/backers.svg)](https://opencollective.com/rxts) | [![prettier-eslint Open Collective backers](https://opencollective.com/prettier-eslint/tiers/backers.svg)](https://opencollective.com/prettier-eslint) | [![prettier-eslint-cli Open Collective backers](https://opencollective.com/prettier-eslint-cli/tiers/backers.svg)](https://opencollective.com/prettier-eslint-cli) |

## Contributing

See [CONTRIBUTING.md](https://github.com/prettier/eslint-plugin-prettier/blob/master/CONTRIBUTING.md)

## Changelog

Detailed changes for each release are documented in [CHANGELOG.md](./CHANGELOG.md).

## License

[MIT](http://opensource.org/licenses/MIT)


code:
import { ESLint } from 'eslint';

declare const eslintPluginPrettier: ESLint.Plugin;

export = eslintPluginPrettier;


code:
/**
 * @file Runs `prettier` as an ESLint rule.
 * @author Andres Suarez
 */

// @ts-check

/**
 * @typedef {import('eslint').AST.Range} Range
 * @typedef {import('eslint').AST.SourceLocation} SourceLocation
 * @typedef {import('eslint').ESLint.Plugin} Plugin
 * @typedef {import('eslint').ESLint.ObjectMetaProperties} ObjectMetaProperties
 * @typedef {import('prettier').FileInfoOptions} FileInfoOptions
 * @typedef {import('prettier').Options} PrettierOptions
 * @typedef {PrettierOptions & { onDiskFilepath: string, parserMeta?: ObjectMetaProperties['meta'], parserPath?: string, usePrettierrc?: boolean }} Options
 */

'use strict';

// ------------------------------------------------------------------------------
//  Requirements
// ------------------------------------------------------------------------------

const {
  showInvisibles,
  generateDifferences,
} = require('prettier-linter-helpers');
const { name, version } = require('./package.json');

// ------------------------------------------------------------------------------
//  Constants
// ------------------------------------------------------------------------------

const { INSERT, DELETE, REPLACE } = generateDifferences;

// ------------------------------------------------------------------------------
//  Privates
// ------------------------------------------------------------------------------

// Lazily-loaded Prettier.
/**
 * @type {(source: string, options: Options, fileInfoOptions: FileInfoOptions) => string}
 */
let prettierFormat;

// ------------------------------------------------------------------------------
//  Rule Definition
// ------------------------------------------------------------------------------

/**
 * Reports a difference.
 *
 * @param {import('eslint').Rule.RuleContext} context - The ESLint rule context.
 * @param {import('prettier-linter-helpers').Difference} difference - The difference object.
 * @returns {void}
 */
function reportDifference(context, difference) {
  const { operation, offset, deleteText = '', insertText = '' } = difference;
  const range = /** @type {Range} */ ([offset, offset + deleteText.length]);
  // `context.getSourceCode()` was deprecated in ESLint v8.40.0 and replaced
  // with the `sourceCode` property.
  // TODO: Only use property when our eslint peerDependency is >=8.40.0.
  const [start, end] = range.map(index =>
    (context.sourceCode ?? context.getSourceCode()).getLocFromIndex(index),
  );

  context.report({
    messageId: operation,
    data: {
      deleteText: showInvisibles(deleteText),
      insertText: showInvisibles(insertText),
    },
    loc: { start, end },
    fix: fixer => fixer.replaceTextRange(range, insertText),
  });
}

// ------------------------------------------------------------------------------
//  Module Definition
// ------------------------------------------------------------------------------

/**
 * @type {Plugin}
 */
const eslintPluginPrettier = {
  meta: { name, version },
  configs: {
    recommended: {
      extends: ['prettier'],
      plugins: ['prettier'],
      rules: {
        'prettier/prettier': 'error',
        'arrow-body-style': 'off',
        'prefer-arrow-callback': 'off',
      },
    },
  },
  rules: {
    prettier: {
      meta: {
        docs: {
          url: 'https://github.com/prettier/eslint-plugin-prettier#options',
        },
        type: 'layout',
        fixable: 'code',
        schema: [
          // Prettier options:
          {
            type: 'object',
            properties: {},
            additionalProperties: true,
          },
          {
            type: 'object',
            properties: {
              usePrettierrc: { type: 'boolean' },
              fileInfoOptions: {
                type: 'object',
                properties: {},
                additionalProperties: true,
              },
            },
            additionalProperties: true,
          },
        ],
        messages: {
          [INSERT]: 'Insert `{{ insertText }}`',
          [DELETE]: 'Delete `{{ deleteText }}`',
          [REPLACE]: 'Replace `{{ deleteText }}` with `{{ insertText }}`',
        },
      },
      create(context) {
        const usePrettierrc =
          !context.options[1] || context.options[1].usePrettierrc !== false;
        /**
         * @type {FileInfoOptions}
         */
        const fileInfoOptions =
          (context.options[1] && context.options[1].fileInfoOptions) || {};

        // `context.getSourceCode()` was deprecated in ESLint v8.40.0 and replaced
        // with the `sourceCode` property.
        // TODO: Only use property when our eslint peerDependency is >=8.40.0.
        const sourceCode = context.sourceCode ?? context.getSourceCode();
        // `context.getFilename()` was deprecated in ESLint v8.40.0 and replaced
        // with the `filename` property.
        // TODO: Only use property when our eslint peerDependency is >=8.40.0.
        const filepath = context.filename ?? context.getFilename();

        // Processors that extract content from a file, such as the markdown
        // plugin extracting fenced code blocks may choose to specify virtual
        // file paths. If this is the case then we need to resolve prettier
        // config and file info using the on-disk path instead of the virtual
        // path.
        // `context.getPhysicalFilename()` was deprecated in ESLint v8.40.0 and replaced
        // with the `physicalFilename` property.
        // TODO: Only use property when our eslint peerDependency is >=8.40.0.
        const onDiskFilepath =
          context.physicalFilename ?? context.getPhysicalFilename();
        const source = sourceCode.text;

        return {
          Program() {
            if (!prettierFormat) {
              // Prettier is expensive to load, so only load it if needed.
              prettierFormat = require('synckit').createSyncFn(
                require.resolve('./worker'),
              );
            }

            /**
             * @type {PrettierOptions}
             */
            const eslintPrettierOptions = context.options[0] || {};

            const parser = context.languageOptions?.parser;

            // prettier.format() may throw a SyntaxError if it cannot parse the
            // source code it is given. Usually for JS files this isn't a
            // problem as ESLint will report invalid syntax before trying to
            // pass it to the prettier plugin. However this might be a problem
            // for non-JS languages that are handled by a plugin. Notably Vue
            // files throw an error if they contain unclosed elements, such as
            // `<template><div></template>. In this case report an error at the
            // point at which parsing failed.
            /**
             * @type {string}
             */
            let prettierSource;
            try {
              prettierSource = prettierFormat(
                source,
                {
                  ...eslintPrettierOptions,
                  filepath,
                  onDiskFilepath,
                  parserMeta:
                    parser &&
                    (parser.meta ?? {
                      name: parser.name,
                      version: parser.version,
                    }),
                  parserPath: context.parserPath,
                  usePrettierrc,
                },
                fileInfoOptions,
              );
            } catch (err) {
              if (!(err instanceof SyntaxError)) {
                throw err;
              }

              let message = 'Parsing error: ' + err.message;

              const error =
                /** @type {SyntaxError & {codeFrame: string; loc: SourceLocation}} */ (
                  err
                );

              // Prettier's message contains a codeframe style preview of the
              // invalid code and the line/column at which the error occurred.
              // ESLint shows those pieces of information elsewhere already so
              // remove them from the message
              if (error.codeFrame) {
                message = message.replace(`\n${error.codeFrame}`, '');
              }
              if (error.loc) {
                message = message.replace(/ \(\d+:\d+\)$/, '');
              }

              context.report({ message, loc: error.loc });

              return;
            }

            if (prettierSource == null) {
              return;
            }

            if (source !== prettierSource) {
              const differences = generateDifferences(source, prettierSource);

              for (const difference of differences) {
                reportDifference(context, difference);
              }
            }
          },
        };
      },
    },
  },
};

module.exports = eslintPluginPrettier;


eslint-plugin-prettier

description: Runs prettier as an eslint rule

author: Teddy Katz

keywords: eslint eslintplugin eslint-plugin prettier

check: prettier --check . && pnpm lint

format: prettier --write . && pnpm lint --fix

lint: eslint . --cache -f friendly --max-warnings 10

release: pnpm check && pnpm test && changeset publish

test: pnpm lint && mocha


code:
import { Linter } from 'eslint';

declare const recommendedConfig: Linter.FlatConfig;

export = recommendedConfig;


code:
const eslintConfigPrettier = require('eslint-config-prettier');
const eslintPluginPrettier = require('./eslint-plugin-prettier');

// Merge the contents of eslint-config-prettier into every
module.exports = {
  ...eslintConfigPrettier,
  plugins: {
    ...eslintConfigPrettier.plugins,
    prettier: eslintPluginPrettier,
  },
  rules: {
    ...eslintConfigPrettier.rules,
    'prettier/prettier': 'error',
    'arrow-body-style': 'off',
    'prefer-arrow-callback': 'off',
  },
};