diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..0f17867 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..9070533 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,20 @@ +name: Node CI + +on: [push] + +jobs: + checks: + runs-on: ubuntu-latest + strategy: + matrix: + node: ['20', '22', '24'] + name: Node ${{ matrix.node }} + + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 + with: + node-version: ${{ matrix.node }} + - run: npm ci + - run: npm run lint + - run: npm test diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..e769079 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,51 @@ +name: Release + +on: + workflow_dispatch: + inputs: + VERSION_BUMP: + description: 'The version bump' + type: choice + options: + - major + - minor + - patch + default: minor + required: true + +jobs: + release: + runs-on: ubuntu-latest + concurrency: + group: release-${{ github.repository }}-${{ github.ref_name }} + cancel-in-progress: false + environment: release + permissions: + contents: write + id-token: write + + steps: + - name: Checkout code + uses: actions/checkout@v6 + with: + persist-credentials: false + + - name: Setup Node.js version + uses: actions/setup-node@v6 + with: + node-version: 24 + registry-url: 'https://registry.npmjs.org/' + + - name: Install dependencies + run: npm ci + + - name: Configure git + run: | + git config user.name "Uphold" + git config user.email "bot@uphold.com" + git config --global url.https://${{ secrets.RELEASE_GITHUB_TOKEN }}@github.com/.insteadOf https://github.com/ + + - name: Generate release + env: + GITHUB_TOKEN: ${{ secrets.RELEASE_GITHUB_TOKEN }} + run: npm run release -- --increment "${{ github.event.inputs.VERSION_BUMP }}" -V diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..09ff3d4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +.eslintcache +yarn.lock diff --git a/.release-it.json b/.release-it.json new file mode 100644 index 0000000..ea1f776 --- /dev/null +++ b/.release-it.json @@ -0,0 +1,25 @@ +{ + "git": { + "changelog": "echo \"## Changelog\\n\\n$(npx @uphold/github-changelog-generator -f unreleased | tail -n +4)\"", + "requireCleanWorkingDir": false, + "commitMessage": "Release `v${version}`", + "requireBranch": "master", + "requireCommits": true, + "tagName": "v${version}" + }, + "github": { + "release": true, + "releaseName": "v${version}" + }, + "hooks": { + "after:bump": [ + "touch CHANGELOG.md", + "echo \"$(npx @uphold/github-changelog-generator -f v${version})\n$(tail -n +2 CHANGELOG.md)\" > CHANGELOG.md", + "git add CHANGELOG.md --all" + ] + }, + "npm": { + "publish": true, + "skipChecks": true + } +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..825c32f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1 @@ +# Changelog diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d22dedc --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Uphold + +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. diff --git a/README.md b/README.md index ba76d6e..a6aded1 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,263 @@ # eslint-plugin-sort-declarations + An ESLint plugin to sort module-level and block-level declarations. + +## Status + +[![npm version][npm-image]][npm-url] +[![build status][ci-image]][ci-url] + +## Installation + +You'll first need to install [ESLint](https://eslint.org/): + +```sh +npm install eslint --save-dev +``` + +Next, install `eslint-plugin-sort-declarations`: + +```sh +npm install eslint-plugin-sort-declarations --save-dev +``` + +## Configuration + +```js +// eslint.config.js +const sortDeclarations = require('eslint-plugin-sort-declarations'); + +module.exports = [ + { + plugins: { + 'sort-declarations': sortDeclarations + }, + rules: { + 'sort-declarations/sort-export-declarations': 'error', + 'sort-declarations/sort-module-declarations': 'error', + 'sort-declarations/sort-variable-declarations': 'error' + } + } +]; +``` + +## Rules + +### `sort-export-declarations` + +Enforces a consistent ordering of module-level `export` statements that don't declare a value (`export * from '...'`, `export { name } from '...'`, `export { name };`). Declarations are grouped and sorted as follows: + +1. Local bindings — `export { name };` with no `from` source +2. Non-local re-exports — `export * from 'react'`, `export { name } from 'lodash'` +3. Local re-exports — `export * from './local'`, `export { name } from './local'` + +Within each group, declarations are sorted alphabetically (case-sensitive) by their exported name (for local bindings) or source path (for re-exports). Any non-export statement between export declarations starts a new region, sorted independently of the others. A blank line is required between groups and removed between declarations within the same group. + +This rule is auto-fixable. + +**Example:** + +```js +// ✗ incorrect +export * from './local'; +export * from 'react'; + +// ✓ correct +export * from 'react'; + +export * from './local'; +``` + +### `sort-module-declarations` + +Enforces a consistent ordering of module-level declarations. Declarations are grouped and sorted as follows: + +1. SCREAMING_SNAKE_CASE constants +2. `let` / `var` declarations +3. `const` declarations +4. `const` call/new-expression declarations +5. Functions +6. PropTypes objects + +Within each group, declarations with dependencies on other declarations in the same group come first, then the rest are sorted alphabetically. Blank lines between groups and between dependent declarations are enforced. + +In files containing GraphQL tagged template literals (`gql` / `graphql`), a separate ordering is applied: + +1. Fragments +2. Queries +3. Mutations +4. Subscriptions +5. `let` / `var` declarations +6. `const` declarations (includes SCREAMING_SNAKE) +7. `const` call/new-expression declarations +8. Functions +9. PropTypes objects + +`require()` calls are not sorted and act as region separators — declarations on either side of a `require` are sorted independently. + +This rule is auto-fixable. + +**Example:** + +```js +// ✗ incorrect +const b = 1; +const a = 2; + +// ✓ correct +const a = 2; +const b = 1; +``` + +### `sort-variable-declarations` + +Enforces a consistent ordering of variable declarations both at module level and inside blocks (`BlockStatement`). + +**Sort order within a group:** + +1. Array destructuring (`const [a] = ...`) +2. Object destructuring (`const { a } = ...`) +3. Identifiers (`const a = ...`) + +Within each type, declarations are sorted alphabetically (case-insensitive) by the first key or element name. + +**Block-level sub-groups** — declarations are further ordered by: + +1. General declarations +2. `useMemo` calls +3. `useCallback` calls + +In blocks, all variable declarations must appear before any non-variable statements. `IfStatement` nodes act as region separators — declarations on either side are sorted independently. + +**Blank line rules:** + +- A blank line is required between a declaration and one that depends on it. +- A blank line is required between multi-line declarations. +- A blank line is required between different sub-groups. +- Blank lines between non-dependent one-liners in the same group are removed. + +**Exclusions:** + +- `require()` calls are not sorted. +- Declarations with multiple declarators (`const a = 1, b = 2`) are not sorted. +- A `'worklet'` directive at the top of a block is skipped. +- Groups containing inter-dependencies are not reordered (dependency order is preserved). + +This rule is auto-fixable. + +**Example:** + +```js +// ✗ incorrect +const { z, a } = obj; + +// ✓ correct +const { a, z } = obj; +``` + +#### Safety model — when sorting is suppressed + +Reordering variable declarations is only safe when the reorder is **observably equivalent** to the original — i.e. no caller can tell which order ran. Most of the time that's true (declarations are independent), but it's not always true: an initializer can have side effects (mutations, async work, IO, throws), or read state that an earlier statement mutated. The rule cannot prove purity from static analysis alone, so it relies on a layered allowlist of shapes that are safe by construction or by convention. + +The rule **suppresses sorting** for any group containing a declaration whose initializer is not on the allowlist. This is intentionally conservative — false negatives (a safe sort skipped) are cosmetic; false positives (an unsafe sort applied) corrupt program behaviour. Past examples of false positives that motivated this design: + +- `setTimeout(() => abort(), t)` placed _after_ `await fetch(...)` → abort timer never fires. +- `parts.join('-')` placed _before_ `parts.pop()` → pop mutates after the join, joined string is wrong. +- `await stepPromise` placed _before_ the `callback()` that triggers it → promise hangs forever. +- `const oldestAllowedDate = new Date(todayAtMidnight)` reordered relative to `const todayAtMidnight = new Date()` — non-deterministic timestamp swap. + +#### Allowlist layers + +An initializer is considered safe to reorder if it falls into one of these layers (defined in `lib/rules/sort-variable-declarations.js`): + +1. **Pure value shapes** — literals, identifiers, `this`, function/arrow/class expressions, JSX, member access, unary/binary/logical/conditional expressions, array/object literals, template literals (with pure interpolations), and TypeScript casts. Recursive: a parent shape is pure only if all its children are pure. + +2. **Hook calls** — any call whose callee starts with `use` (`useFoo(...)`, `Namespace.useBar(...)`). React's rules-of-hooks guarantees these are called in the same order on every render once the source is fixed. + +3. **Known-pure built-in callees** — `Number`, `String`, `Boolean`, `BigInt`, `parseInt`, `parseFloat`, `isNaN`, `isFinite`, `Object.{freeze, keys, values, entries, fromEntries, create, ...}`, `Array.{from, of, isArray}`, `Math.{abs, ceil, floor, round, sqrt, pow, max, min, ...}` (excluding `random`), `JSON.{stringify, parse}`, `Number.{isInteger, isFinite, ...}`, `String.{fromCharCode, fromCodePoint}`, `Symbol.{for, keyFor}` (excluding the `Symbol(...)` constructor), `PropTypes.{any, array, arrayOf, bool, element, elementType, exact, func, instanceOf, node, number, object, objectOf, oneOf, oneOfType, shape, string, symbol}` (validator factories only — excludes side-effecting `checkPropTypes`/`resetWarningCache`), plus a curated set of String/Array prototype reads (`toLowerCase`, `toUpperCase`, `trim`, `slice`, `concat`, `at`, `indexOf`, `lastIndexOf`, `includes`, `split`, `match`, `padStart`, `padEnd`, `toString`, `valueOf`, `toFixed`, etc.). All require their arguments to be pure. + +4. **Project-convention callees** — identifier callees matching `get*`, `is*`, `has*`, `format*` (with the next character uppercase, so `getter`/`formatter`/`gettext` are correctly rejected) plus the bare `t` (i18n). Member calls are accepted when the namespace is a PascalCase identifier (`DateHelper.getFormattedDate`, `I18n.t`, `IdentityHelper.getIdentity`); lowercase namespaces (`localStorage.getItem`, `obj.get`, `props.handler`) are rejected because they could be IO, mutation, or arbitrary instance methods. + +#### Cross-statement guard + +When the rule moves variable declarations across non-variable statements (the _vars-before-non-vars_ reorder), it applies a stricter check than the within-block sort: it bails on **any** non-hook call in the moved declaration's initializer, even if that call is on the allowlist. The reason is that the moved declaration may _read state mutated by the non-var statement_ it crosses (e.g., `MockDate.set(now); const r = isDateInLastMonth(date)` — `isDateInLastMonth` reads the mocked `Date.now()`, which the prior statement just set). Hook calls are excepted because hooks are conventionally called at the top of components and are not interleaved with mutating statements. + +#### Known limitations and accepted false negatives + +These shapes are **rejected** by the allowlist even though they are usually safe in practice. Sorting is suppressed for groups containing them: + +- `new Blob(...)`, `new FormData()`, `new XMLHttpRequest()`, `new Proxy(...)`, `new BigNumber(...)`, `new MyClass()` — all `NewExpression` is blocked because the rule can't distinguish value-semantic constructors from non-deterministic (`new Date()`) or registration-side-effecting (`new EventEmitter()`) ones. +- `arr.map(fn)` / `.filter(fn)` / `.find(fn)` / `.some(fn)` / `.every(fn)` / `.reduce(fn)` — the callback is invoked synchronously during the call. Pure if the callback is a projection (`x => x.foo`); unsafe if the callback has side effects. The rule has no way to verify callback purity. +- `.format(...)`, `.gt(x)`, `.lt(x)`, `.div(x)`, `.isZero()` — library-specific value methods (BigNumber, day.js, Intl). Pure in their own libraries but the property name alone doesn't prove that. To allowlist these, you'd need to either expand the prototype-methods set (risk of false positives) or relax the PascalCase requirement on member calls (risk of allowing `obj.set(...)`-style mutations). +- `localStorage.getItem(...)`, `sessionStorage.getItem(...)`, `path.join(...)`, `queryString.parse(...)` — pure reads on lowercase namespaces. Could be allowlisted by adding the namespace to `KNOWN_PURE_NAMESPACES` if you accept the risk that another module of the same name might shadow them. +- `form.watch(...)`, `handleSubmit(...)`, `getValues()` (react-hook-form) — read-only subscriptions / pure factories. Allowlisting them would require either an explicit member-name list or relaxing the PascalCase requirement. + +These shapes are **accepted** by the allowlist even though they could be unsafe under specific anti-conventions: + +- `getOrCreateUser(...)` — matches `get*`, treated as pure, but actually mutates if missing. The rule cannot detect this from the name alone. +- `getNextId()` — matches `get*`, but advances a counter on each call. Order matters but the rule won't see it. +- `isAuthenticated()` — typically a pure predicate, but might trigger token refresh as a side effect. +- Plain member access `obj.foo` — could be a getter property with side effects (rare in practice but legal). + +The accepted-false-positives set is small in practice: in an audit of ~360 affected files in the host codebase, only two project-helper callees turned out to be borderline (`getRootNavigator` returning a navigation singleton, `getGeolocation` reading `document.cookie`). + +#### How to tighten or loosen + +To make the rule **more conservative** (skip more sorts): + +- Remove entries from `KNOWN_PURE_GLOBAL_FNS`, `KNOWN_PURE_NAMESPACES`, `KNOWN_PURE_PROTOTYPE_METHODS`, or `PROJECT_CONVENTION_PREFIXES` in `lib/rules/sort-variable-declarations.js`. +- Drop `'t'` from `PROJECT_CONVENTION_EXACT_NAMES` if your codebase has non-i18n functions named `t`. +- Add the PascalCase namespace requirement to the bare-identifier project-convention check (currently bare identifiers like `getFoo()` are accepted regardless of where they're imported from). +- Drop the project-convention layer entirely — sorting only applies to pure value shapes, hooks, and built-ins. + +To make the rule **more permissive** (sort more aggressively): + +- Add specific lowercase namespaces to `KNOWN_PURE_NAMESPACES` (`form`, `localStorage`, `path`, `queryString`) if your team is confident those names are not shadowed by mutating instances. +- Add prototype methods to `KNOWN_PURE_PROTOTYPE_METHODS` for libraries you rely on (`gt`, `lt`, `isZero`, `decimalPlaces`, `toISOString`, `getMonth`, etc.). Watch for ambiguous names — `replace` is excluded because of `history.replace(...)` / `router.replace(...)`. +- Allowlist specific `NewExpression` callees — e.g., a `KNOWN_PURE_CONSTRUCTORS = new Set(['Blob', 'FormData', 'XMLHttpRequest', 'Proxy', 'BigNumber'])`. Note that `new Date()` and `new EventEmitter()` should NEVER be on this list. +- Relax the project-convention member-call check to allow lowercase namespaces (this widens coverage to `localStorage.getItem`, `obj.getX`, etc., at the cost of accepting `cache.getOrCreate(...)`-style anti-conventions). + +When loosening, audit a sample of affected files to confirm the new sorts are observably safe. The test file (`tests/sort-variable-declarations.js`) documents many edge cases — both `valid` (must-not-sort) and `invalid` (must-sort) — which serve as a regression suite for any allowlist change. + +#### Risk taxonomy (what _could_ break if the rule were too permissive) + +These are the categories of order-sensitive code the rule is designed to avoid touching. The test file contains a `valid` test for each: + +1. **Side-effecting calls** — `arr.pop()`, `set.add(x)`, `nextId()`, `logger.info(...)`, `iter.next()`. +2. **Non-deterministic calls** — `Date.now()`, `performance.now()`, `Math.random()`, `crypto.randomBytes()`, `uuid()`, `new Date()`. +3. **Promise creation order** — `fetchFoo(); fetchBar();` (network requests fire on creation). +4. **Throw ordering** — `parseStrict(input); audit(input);` — order changes which one throws first and which side effects already happened. +5. **Tagged templates** — `` sql`...` ``, `` gql`...` `` — the tag function executes on creation. +6. **`let` reassigned downstream** — only matters if the initializer is itself impure (covered by category 1). +7. **Getter side effects on member access** — known limitation; rule trusts the convention. +8. **Stateful iterables consumed via spread** — `[...gen()]` (spread of a call) is correctly blocked; `[...sharedIter]` (spread of an identifier) is a known limitation. +9. **`new Expression` with side effects** — `new EventEmitter()`, `new AbortController()` — all `NewExpression` is blocked uniformly. + +## License + +[MIT](https://opensource.org/licenses/MIT) + +## Contributing + +### Development + +Install dependencies: + +```bash +npm i +``` + +Run tests: + +```bash +npm run test +``` + +### Cutting a release + +The release process is automated via the [release](https://github.com/uphold/eslint-plugin-sort-declarations/actions/workflows/release.yaml) GitHub workflow. Run it by clicking the "Run workflow" button. + +[npm-image]: https://img.shields.io/npm/v/eslint-plugin-sort-declarations.svg +[npm-url]: https://www.npmjs.com/package/eslint-plugin-sort-declarations +[ci-image]: https://github.com/uphold/eslint-plugin-sort-declarations/actions/workflows/ci.yml/badge.svg?branch=master +[ci-url]: https://github.com/uphold/eslint-plugin-sort-declarations/actions/workflows/ci.yml diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..e01261a --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,40 @@ +'use strict'; + +const eslintPluginPrettierRecommended = require('eslint-plugin-prettier/recommended'); +const js = require('@eslint/js'); +const sortDeclarations = require('./lib/index.js'); + +module.exports = [ + js.configs.recommended, + { + languageOptions: { + globals: { + __dirname: 'readonly', + console: 'readonly', + exports: 'writable', + module: 'writable', + process: 'readonly', + require: 'readonly' + }, + sourceType: 'commonjs' + }, + name: 'eslint-plugin-sort-declarations/eslint-config', + plugins: { + 'sort-declarations': sortDeclarations + }, + rules: { + 'prettier/prettier': [ + 'error', + { + arrowParens: 'avoid', + printWidth: 120, + singleQuote: true, + trailingComma: 'none' + } + ], + 'sort-declarations/sort-module-declarations': 'error', + 'sort-declarations/sort-variable-declarations': 'error' + } + }, + eslintPluginPrettierRecommended +]; diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..ca883af --- /dev/null +++ b/lib/index.js @@ -0,0 +1,11 @@ +'use strict'; +const { name, version } = require('../package.json'); + +module.exports = Object.freeze({ + meta: { name, namespace: 'sort-declarations', version }, + rules: { + 'sort-export-declarations': require('./rules/sort-export-declarations'), + 'sort-module-declarations': require('./rules/sort-module-declarations'), + 'sort-variable-declarations': require('./rules/sort-variable-declarations') + } +}); diff --git a/lib/rules/sort-export-declarations.js b/lib/rules/sort-export-declarations.js new file mode 100644 index 0000000..294845e --- /dev/null +++ b/lib/rules/sort-export-declarations.js @@ -0,0 +1,204 @@ +'use strict'; + +const { compareStrings, getTrailingLineComment } = require('../utils'); + +const GROUPS = { + LOCAL: 3, + LOCAL_BINDING: 1, + NON_LOCAL: 2 +}; + +function getAttachedLeadingComments(node, sourceCode) { + const attached = []; + const comments = sourceCode.getCommentsBefore(node); + let nextLine = node.loc.start.line; + + for (let i = comments.length - 1; i >= 0; i--) { + const comment = comments[i]; + + const tokenBefore = sourceCode.getTokenBefore(comment, { includeComments: false }); + + const isInline = tokenBefore !== null && tokenBefore.loc.end.line === comment.loc.start.line; + + if (isInline) break; + + if (comment.loc.end.line === nextLine - 1) { + attached.unshift(comment); + nextLine = comment.loc.start.line; + } else { + break; + } + } + + return attached; +} + +function getEffectiveStartLine(node, sourceCode) { + const comments = getAttachedLeadingComments(node, sourceCode); + + return comments.length > 0 ? comments[0].loc.start.line : node.loc.start.line; +} + +function getFullRange(node, sourceCode) { + const leadingComments = getAttachedLeadingComments(node, sourceCode); + const trailingComment = getTrailingLineComment(node, sourceCode); + + const end = trailingComment ? trailingComment.range[1] : node.range[1]; + const start = leadingComments.length > 0 ? leadingComments[0].range[0] : node.range[0]; + + return [start, end]; +} + +function getFullText(node, sourceCode) { + const [start, end] = getFullRange(node, sourceCode); + + return sourceCode.text.slice(start, end); +} + +function getGroup(node) { + if (node.type === 'ExportNamedDeclaration' && node.source === null) { + return GROUPS.LOCAL_BINDING; + } + + return node.source.value.startsWith('.') ? GROUPS.LOCAL : GROUPS.NON_LOCAL; +} + +function getSortKey(node) { + if (node.type === 'ExportNamedDeclaration' && node.source === null) { + return node.specifiers[0]?.exported?.name ?? ''; + } + + return node.source.value; +} + +function compare(nodeA, nodeB) { + const groupA = getGroup(nodeA); + const groupB = getGroup(nodeB); + + if (groupA !== groupB) return groupA - groupB; + + const keyA = getSortKey(nodeA); + const keyB = getSortKey(nodeB); + + return compareStrings(keyA, keyB); +} + +function isExportStatement(node) { + return node.type === 'ExportAllDeclaration' || (node.type === 'ExportNamedDeclaration' && node.declaration === null); +} + +function getRegions(body) { + let current = []; + const regions = []; + + for (const node of body) { + if (isExportStatement(node)) { + current.push(node); + } else if (current.length > 0) { + regions.push(current); + current = []; + } + } + + if (current.length > 0) regions.push(current); + + return regions; +} + +function needsBlankLine(prev, curr) { + return getGroup(prev) !== getGroup(curr); +} + +module.exports = { + create(context) { + const { sourceCode } = context; + + return { + Program(programNode) { + for (const region of getRegions(programNode.body)) { + if (region.length < 2) continue; + + const correctOrder = [...region].sort(compare); + + const firstViolation = region.findIndex((node, i) => node !== correctOrder[i]); + + if (firstViolation !== -1) { + context.report({ + fix(fixer) { + const parts = []; + + for (let i = 0; i < correctOrder.length; i++) { + const curr = correctOrder[i]; + const prev = correctOrder[i - 1]; + + if (i > 0 && needsBlankLine(prev, curr)) { + parts.push(''); + } + + parts.push(getFullText(curr, sourceCode)); + } + + const [, endRange] = getFullRange(region[region.length - 1], sourceCode); + const [startRange] = getFullRange(region[0], sourceCode); + + return fixer.replaceTextRange([startRange, endRange], parts.join('\n')); + }, + messageId: 'sortOrder', + node: region[firstViolation] + }); + + continue; + } + + for (let i = 1; i < region.length; i++) { + const curr = region[i]; + const prev = region[i - 1]; + + const currStart = getEffectiveStartLine(curr, sourceCode); + const trailingComment = getTrailingLineComment(prev, sourceCode); + + const effectivePrevEnd = trailingComment ? trailingComment.loc.end.line : prev.loc.end.line; + + const hasBlank = currStart - effectivePrevEnd > 1; + + if (needsBlankLine(prev, curr) && !hasBlank) { + const [rangeStart] = getFullRange(curr, sourceCode); + + context.report({ + fix(fixer) { + return fixer.replaceTextRange([rangeStart, rangeStart], '\n'); + }, + messageId: 'missingBlankLine', + node: curr + }); + } + + if (!needsBlankLine(prev, curr) && hasBlank) { + const [currRangeStart] = getFullRange(curr, sourceCode); + const [, prevRangeEnd] = getFullRange(prev, sourceCode); + + context.report({ + fix(fixer) { + return fixer.replaceTextRange([prevRangeEnd, currRangeStart], '\n'); + }, + messageId: 'extraBlankLine', + node: curr + }); + } + } + } + } + }; + }, + meta: { + fixable: 'code', + messages: { + extraBlankLine: 'Remove the blank line before this export.', + missingBlankLine: 'Add a blank line before this export.', + sortOrder: + 'Export declarations should be sorted: named local exports first, then non-local re-exports, then local re-exports — all alphabetically within each group.' + }, + schema: [], + type: 'suggestion' + } +}; diff --git a/lib/rules/sort-module-declarations.js b/lib/rules/sort-module-declarations.js new file mode 100644 index 0000000..5b58b85 --- /dev/null +++ b/lib/rules/sort-module-declarations.js @@ -0,0 +1,661 @@ +'use strict'; + +const { + SKIP_KEYS, + collectPatternNames, + collectTypeQueryRefs, + collectUsedIdentifiers, + compareStrings, + getAllDeclaredNames, + getTrailingLineComment, + isOneLiner, + topoSortAlpha +} = require('../utils'); + +const GQL_GROUPS = { + CONST: 2, + CONST_CALL: 3, + FRAGMENT: 4, + FUNCTION: 8, + LET_OR_VAR: 1, + MUTATION: 6, + PROP_TYPES: 9, + QUERY: 5, + SUBSCRIPTION: 7 +}; + +const GQL_TAGS = new Set(['gql', 'graphql']); + +const GROUPS = { + CONST: 3, + CONST_CALL: 4, + FUNCTION: 5, + LET_OR_VAR: 2, + PROP_TYPES: 6, + SCREAMING_SNAKE: 1 +}; + +const SCREAMING_SNAKE_RE = /^[A-Z][A-Z0-9_]*$/; + +function collectLocalBindings(node, names) { + if (!node || typeof node !== 'object') return; + + if (Array.isArray(node)) { + for (const child of node) collectLocalBindings(child, names); + + return; + } + + if (!node.type) return; + + if (node.type === 'ArrowFunctionExpression' || node.type === 'FunctionExpression') { + for (const param of node.params) collectPatternNames(param, names); + collectLocalBindings(node.body, names); + + return; + } + + if (node.type === 'FunctionDeclaration') { + if (node.id) names.add(node.id.name); + + return; + } + + if (node.type === 'VariableDeclarator') { + collectPatternNames(node.id, names); + } + + if (node.type === 'CatchClause' && node.param) { + collectPatternNames(node.param, names); + } + + for (const key of Object.keys(node)) { + if (SKIP_KEYS.has(key)) continue; + collectLocalBindings(node[key], names); + } +} + +function containsPropTypesRef(node) { + if (!node || typeof node !== 'object') return false; + if (Array.isArray(node)) return node.some(containsPropTypesRef); + + if (node.type === 'MemberExpression' && node.object?.type === 'Identifier' && node.object?.name === 'PropTypes') + return true; + + for (const key of Object.keys(node)) { + if (SKIP_KEYS.has(key)) continue; + if (containsPropTypesRef(node[key])) return true; + } + + return false; +} + +function getAttachedLeadingComments(node, sourceCode) { + const attached = []; + const comments = sourceCode.getCommentsBefore(node); + let nextLine = node.loc.start.line; + + for (let i = comments.length - 1; i >= 0; i--) { + const comment = comments[i]; + + const isBlockWithOneBlankLine = comment.type === 'Block' && comment.loc.end.line === nextLine - 2; + const tokenBefore = sourceCode.getTokenBefore(comment, { includeComments: false }); + + const isInline = tokenBefore !== null && tokenBefore.loc.end.line === comment.loc.start.line; + + if (isInline) break; + + if (comment.loc.end.line === nextLine - 1 || isBlockWithOneBlankLine) { + attached.unshift(comment); + nextLine = comment.loc.start.line; + } else { + break; + } + } + + return attached; +} + +function getEffectiveStartLine(node, sourceCode) { + const comments = getAttachedLeadingComments(node, sourceCode); + + return comments.length > 0 ? comments[0].loc.start.line : node.loc.start.line; +} + +function getFullRange(node, sourceCode) { + const leadingComments = getAttachedLeadingComments(node, sourceCode); + const trailingComment = getTrailingLineComment(node, sourceCode); + + const end = trailingComment ? trailingComment.range[1] : node.range[1]; + const start = leadingComments.length > 0 ? leadingComments[0].range[0] : node.range[0]; + + return [start, end]; +} + +function getFullText(node, sourceCode) { + const [start, end] = getFullRange(node, sourceCode); + + return sourceCode.text.slice(start, end); +} + +function hasDependency(nodeA, nodeB, depsMap) { + const aDeps = depsMap.get(nodeA) ?? new Set(); + const bDeps = depsMap.get(nodeB) ?? new Set(); + + return aDeps.has(nodeB) || bDeps.has(nodeA); +} + +function buildSandwichedNodeSet(correctOrder, groupMap, depsMap) { + const byGroup = new Map(); + + for (const node of correctOrder) { + const gid = groupMap.get(node); + + if (!byGroup.has(gid)) byGroup.set(gid, []); + byGroup.get(gid).push(node); + } + + const sandwichedNodes = new Set(); + + for (const [, nodes] of byGroup) { + if (!nodes.some(node => !isOneLiner(node))) continue; + + let groupHasSandwich = false; + let i = 0; + + while (i < nodes.length) { + if (!isOneLiner(nodes[i])) { + i++; + continue; + } + + let j = i + 1; + + while (j < nodes.length && isOneLiner(nodes[j]) && !hasDependency(nodes[j - 1], nodes[j], depsMap)) { + j++; + } + + if (j - i >= 2) { + const hasAfter = j < nodes.length && !isOneLiner(nodes[j]); + const hasBefore = i > 0 && !isOneLiner(nodes[i - 1]); + + if (hasBefore && hasAfter) { + groupHasSandwich = true; + } + } + + i = j; + } + + if (groupHasSandwich) { + for (const node of nodes) { + sandwichedNodes.add(node); + } + } + } + + return sandwichedNodes; +} + +function unwrap(node) { + return node.type === 'ExportNamedDeclaration' ? node.declaration : node; +} + +function getBody(node) { + const decl = unwrap(node); + + if (!decl) return null; + if (decl.type === 'FunctionDeclaration') return decl.body; + if (decl.type === 'VariableDeclaration') return decl.declarations[0]?.init ?? null; + + return null; +} + +function getGqlCategory(node) { + const decl = unwrap(node); + + if (decl?.type !== 'VariableDeclaration') return null; + const init = decl.declarations[0]?.init; + + if (!init || init.type !== 'TaggedTemplateExpression') return null; + const tagName = init.tag.type === 'Identifier' ? init.tag.name : init.tag.property?.name; + + if (!GQL_TAGS.has(tagName)) return null; + + const firstWord = init.quasi.quasis[0]?.value?.cooked?.trimStart().split(/[\s({]/)[0] ?? ''; + + if (firstWord === 'fragment') return 'fragment'; + if (firstWord === 'query') return 'query'; + if (firstWord === 'mutation') return 'mutation'; + if (firstWord === 'subscription') return 'subscription'; + + return 'fragment'; +} + +function fileHasGraphql(body) { + return body.some(node => getGqlCategory(node) !== null); +} + +function getIdTypeOrder(node) { + const decl = unwrap(node); + + if (decl?.type !== 'VariableDeclaration') return 2; + const id = decl.declarations[0]?.id; + + if (id?.type === 'ArrayPattern') return 0; + if (id?.type === 'ObjectPattern') return 1; + + return 2; +} + +function getLocalBindings(declNode) { + const decl = unwrap(declNode); + const names = new Set(); + + if (!decl) return names; + + if (decl.type === 'FunctionDeclaration') { + for (const param of decl.params) collectPatternNames(param, names); + collectLocalBindings(decl.body, names); + + return names; + } + + if (decl.type === 'VariableDeclaration') { + const init = decl.declarations[0]?.init; + + if (init) collectLocalBindings(init, names); + } + + return names; +} + +function buildDepsMap(declarations) { + const nameToDecl = new Map(); + + for (const decl of declarations) { + for (const name of getAllDeclaredNames(decl)) { + nameToDecl.set(name, decl); + } + } + + const deps = new Map(); + + for (const decl of declarations) { + const body = getBody(decl); + const localNames = getLocalBindings(decl); + const ownNames = getAllDeclaredNames(decl); + + const used = body ? collectUsedIdentifiers(body) : new Set(); + const unwrapped = unwrap(decl); + + if (unwrapped?.type === 'VariableDeclaration') { + const id = unwrapped.declarations[0]?.id; + + if (id?.typeAnnotation) collectTypeQueryRefs(id.typeAnnotation, used); + } + + deps.set( + decl, + new Set( + [...used] + .filter(ident => nameToDecl.has(ident) && !ownNames.has(ident) && !localNames.has(ident)) + .map(ident => nameToDecl.get(ident)) + ) + ); + } + + return { deps }; +} + +function getName(node) { + const decl = unwrap(node); + + if (!decl) return null; + + if (decl.type === 'FunctionDeclaration') return decl.id?.name ?? null; + + if (decl.type === 'VariableDeclaration' && decl.declarations.length === 1) { + const [{ id }] = decl.declarations; + + if (!id) return null; + if (id.type === 'Identifier') return id.name; + + if (id.type === 'ArrayPattern') { + const first = id.elements.find(Boolean); + + return first?.type === 'Identifier' ? first.name : null; + } + + if (id.type === 'ObjectPattern') { + const [first] = id.properties; + + if (first?.type === 'Property' && first.key?.type === 'Identifier') return first.key.name; + } + } + + return null; +} + +function compareNames(nodeA, nodeB) { + const orderA = getIdTypeOrder(nodeA); + const orderB = getIdTypeOrder(nodeB); + + if (orderA !== orderB) return orderA - orderB; + + const nameA = getName(nodeA) ?? ''; + const nameB = getName(nodeB) ?? ''; + + return compareStrings(nameA, nameB); +} + +function isCallExpressionConst(node) { + const decl = unwrap(node); + + if (decl?.type !== 'VariableDeclaration') return false; + const init = decl.declarations[0]?.init; + + if (!init) return false; + if (init.type === 'CallExpression' || init.type === 'NewExpression') return true; + if (init.type === 'BinaryExpression') { + const { left, right } = init; + + return ( + left?.type === 'CallExpression' || + left?.type === 'NewExpression' || + right?.type === 'CallExpression' || + right?.type === 'NewExpression' + ); + } + + return false; +} + +function isFunction(node) { + const decl = unwrap(node); + + if (!decl) return false; + if (decl.type === 'FunctionDeclaration') return true; + + if (decl.type === 'VariableDeclaration') { + const init = decl.declarations[0]?.init; + + if (init?.type === 'ArrowFunctionExpression' || init?.type === 'FunctionExpression') return true; + } + + return false; +} + +function isLetOrVar(node) { + const decl = unwrap(node); + + return decl?.type === 'VariableDeclaration' && (decl.kind === 'let' || decl.kind === 'var'); +} + +function isPropTypes(node) { + const decl = unwrap(node); + + if (decl?.type !== 'VariableDeclaration') return false; + const init = decl.declarations[0]?.init; + + return init?.type === 'ObjectExpression' && containsPropTypesRef(init); +} + +function isRequire(node) { + const decl = unwrap(node); + + if (decl?.type !== 'VariableDeclaration') return false; + const init = decl.declarations[0]?.init; + + return init?.type === 'CallExpression' && init.callee?.type === 'Identifier' && init.callee.name === 'require'; +} + +function isScreamingSnake(node) { + const name = getName(node); + + return name !== null && SCREAMING_SNAKE_RE.test(name); +} + +function getGroup(node) { + if (isPropTypes(node)) return GROUPS.PROP_TYPES; + if (isScreamingSnake(node)) return GROUPS.SCREAMING_SNAKE; + if (isFunction(node)) return GROUPS.FUNCTION; + if (isLetOrVar(node)) return GROUPS.LET_OR_VAR; + if (isCallExpressionConst(node)) return GROUPS.CONST_CALL; + + return GROUPS.CONST; +} + +function getGroupForFile(node, hasGraphql) { + if (isPropTypes(node)) return hasGraphql ? GQL_GROUPS.PROP_TYPES : GROUPS.PROP_TYPES; + + const gqlCat = getGqlCategory(node); + + if (hasGraphql && gqlCat) { + return GQL_GROUPS[gqlCat.toUpperCase()]; + } + + if (hasGraphql) { + if (isFunction(node)) return GQL_GROUPS.FUNCTION; + if (isLetOrVar(node)) return GQL_GROUPS.LET_OR_VAR; + if (isCallExpressionConst(node)) return GQL_GROUPS.CONST_CALL; + + return GQL_GROUPS.CONST; + } + + return getGroup(node); +} + +function buildGroupMap(nodes, hasGraphql) { + const map = new Map(); + + for (const node of nodes) { + map.set(node, getGroupForFile(node, hasGraphql)); + } + + return map; +} + +function isSortable(node) { + return getName(node) !== null && !isRequire(node); +} + +function getSortableRegions(body) { + let current = []; + const regions = []; + + for (const node of body) { + if (isSortable(node)) { + current.push(node); + } else if (current.length > 0) { + regions.push(current); + current = []; + } + } + + if (current.length > 0) regions.push(current); + + return regions; +} + +function makeCompare(groupMap) { + return (nodeA, nodeB) => { + const groupA = groupMap.get(nodeA); + const groupB = groupMap.get(nodeB); + + if (groupA !== groupB) return groupA - groupB; + + return compareNames(nodeA, nodeB); + }; +} + +function computeCorrectOrder(declarations, groupMap) { + const { deps } = buildDepsMap(declarations); + + return topoSortAlpha(declarations, deps, makeCompare(groupMap)); +} + +function wouldSeparateWithoutDep(prev, curr, depsMap, groupMap, sandwichedNodes) { + if (groupMap.get(prev) !== groupMap.get(curr)) return true; + if (hasDependency(prev, curr, depsMap)) return true; + if ((prev.type === 'ExportNamedDeclaration') !== (curr.type === 'ExportNamedDeclaration')) return true; + + if (isOneLiner(prev) && isOneLiner(curr)) { + if (isFunction(prev) && isFunction(curr)) return sandwichedNodes.has(prev) && sandwichedNodes.has(curr); + + return false; + } + + if (isFunction(prev) && isFunction(curr)) return true; + + return true; +} + +function buildDependentInGroupSet(sortedOrder, depsMap, groupMap, sandwichedNodes) { + const dependents = new Set(); + let currentCluster = new Set(); + + for (let i = 0; i < sortedOrder.length; i++) { + const curr = sortedOrder[i]; + + let separated = i === 0 || wouldSeparateWithoutDep(sortedOrder[i - 1], curr, depsMap, groupMap, sandwichedNodes); + + if (!separated) { + const currDeps = depsMap.get(curr) ?? new Set(); + + for (const dep of currDeps) { + if (currentCluster.has(dep)) { + dependents.add(curr); + separated = true; + break; + } + } + } + + if (separated) { + currentCluster = new Set([curr]); + } else { + currentCluster.add(curr); + } + } + + return dependents; +} + +function needsBlankLine(prev, curr, depsMap, { dependentInGroup, groupMap, sandwichedNodes }) { + if (dependentInGroup.has(curr)) return true; + + return wouldSeparateWithoutDep(prev, curr, depsMap, groupMap, sandwichedNodes); +} + +module.exports = { + create(context) { + const { sourceCode } = context; + + return { + Program(programNode) { + const allSortable = programNode.body.filter(isSortable); + const hasGraphql = fileHasGraphql(programNode.body); + + const groupMap = buildGroupMap(allSortable, hasGraphql); + const allDepsMap = buildDepsMap(allSortable).deps; + + for (const region of getSortableRegions(programNode.body)) { + if (region.length < 2) continue; + + const correctOrder = computeCorrectOrder(region, groupMap); + + const sandwichedNodes = buildSandwichedNodeSet(correctOrder, groupMap, allDepsMap); + + const dependentInGroup = buildDependentInGroupSet(correctOrder, allDepsMap, groupMap, sandwichedNodes); + const firstViolation = region.findIndex((decl, i) => getName(decl) !== getName(correctOrder[i])); + + if (firstViolation !== -1) { + context.report({ + fix(fixer) { + const parts = []; + + for (let i = 0; i < correctOrder.length; i++) { + const curr = correctOrder[i]; + const prev = correctOrder[i - 1]; + + if ( + i > 0 && + needsBlankLine(prev, curr, allDepsMap, { dependentInGroup, groupMap, sandwichedNodes, sourceCode }) + ) { + parts.push(''); + } + + parts.push(getFullText(correctOrder[i], sourceCode)); + } + + const [, endRange] = getFullRange(region[region.length - 1], sourceCode); + const [startRange] = getFullRange(region[0], sourceCode); + + return fixer.replaceTextRange([startRange, endRange], parts.join('\n')); + }, + messageId: 'sortOrder', + node: region[firstViolation] + }); + + continue; + } + + for (let i = 1; i < region.length; i++) { + const curr = region[i]; + const prev = region[i - 1]; + + const currStart = getEffectiveStartLine(curr, sourceCode); + const trailingComment = getTrailingLineComment(prev, sourceCode); + + const effectivePrevEnd = trailingComment ? trailingComment.loc.end.line : prev.loc.end.line; + + const hasBlank = currStart - effectivePrevEnd > 1; + + if ( + needsBlankLine(prev, curr, allDepsMap, { dependentInGroup, groupMap, sandwichedNodes, sourceCode }) && + !hasBlank + ) { + const [rangeStart] = getFullRange(curr, sourceCode); + + context.report({ + fix(fixer) { + return fixer.replaceTextRange([rangeStart, rangeStart], '\n'); + }, + messageId: 'missingBlankLine', + node: curr + }); + } + + if ( + !needsBlankLine(prev, curr, allDepsMap, { dependentInGroup, groupMap, sandwichedNodes, sourceCode }) && + hasBlank + ) { + const [currRangeStart] = getFullRange(curr, sourceCode); + const [, prevRangeEnd] = getFullRange(prev, sourceCode); + + context.report({ + fix(fixer) { + return fixer.replaceTextRange([prevRangeEnd, currRangeStart], '\n'); + }, + messageId: 'extraBlankLine', + node: curr + }); + } + } + } + } + }; + }, + meta: { + fixable: 'code', + messages: { + extraBlankLine: 'Remove the blank line before this declaration.', + missingBlankLine: 'Add a blank line before this declaration.', + sortOrder: + 'Module-level declarations should be sorted: SCREAMING_SNAKE constants first, then let/var declarations, then plain const declarations, then const declarations initialized with a call or new expression, then functions, then PropTypes objects. In files with GraphQL: non-GQL declarations come first (let/var, plain const, call-expression const), then GQL declarations (fragments, queries, mutations, subscriptions), then functions, then PropTypes. Within each group: dependencies first, then alphabetically.' + }, + schema: [], + type: 'suggestion' + } +}; diff --git a/lib/rules/sort-variable-declarations.js b/lib/rules/sort-variable-declarations.js new file mode 100644 index 0000000..44ae641 --- /dev/null +++ b/lib/rules/sort-variable-declarations.js @@ -0,0 +1,1084 @@ +'use strict'; + +const { + collectDeclaredNames, + collectTypeQueryRefs, + collectUsedIdentifiers, + compareStrings, + groupHasDependencies, + isOneLiner +} = require('../utils'); + +const KNOWN_PURE_GLOBAL_FNS = new Set([ + 'parseInt', + 'parseFloat', + 'isNaN', + 'isFinite', + 'Number', + 'String', + 'Boolean', + 'BigInt' +]); + +const KNOWN_PURE_NAMESPACES = { + Array: new Set(['from', 'of', 'isArray']), + JSON: new Set(['stringify', 'parse']), + Math: new Set([ + 'abs', + 'ceil', + 'floor', + 'round', + 'trunc', + 'sqrt', + 'cbrt', + 'pow', + 'exp', + 'log', + 'log2', + 'log10', + 'log1p', + 'expm1', + 'sin', + 'cos', + 'tan', + 'asin', + 'acos', + 'atan', + 'atan2', + 'sinh', + 'cosh', + 'tanh', + 'asinh', + 'acosh', + 'atanh', + 'hypot', + 'max', + 'min', + 'sign', + 'fround', + 'clz32', + 'imul' + ]), + Number: new Set(['isInteger', 'isFinite', 'isNaN', 'isSafeInteger', 'parseInt', 'parseFloat']), + Object: new Set([ + 'freeze', + 'keys', + 'values', + 'entries', + 'fromEntries', + 'create', + 'getOwnPropertyNames', + 'getOwnPropertyDescriptor', + 'getPrototypeOf', + 'is', + 'isFrozen', + 'isSealed' + ]), + PropTypes: new Set([ + 'any', + 'array', + 'arrayOf', + 'bool', + 'element', + 'elementType', + 'exact', + 'func', + 'instanceOf', + 'node', + 'number', + 'object', + 'objectOf', + 'oneOf', + 'oneOfType', + 'shape', + 'string', + 'symbol' + ]), + String: new Set(['fromCharCode', 'fromCodePoint', 'raw']), + Symbol: new Set(['for', 'keyFor']) +}; + +const KNOWN_PURE_PROTOTYPE_METHODS = new Set([ + 'at', + 'charAt', + 'charCodeAt', + 'codePointAt', + 'concat', + 'endsWith', + 'includes', + 'indexOf', + 'lastIndexOf', + 'match', + 'matchAll', + 'normalize', + 'padEnd', + 'padStart', + 'repeat', + 'slice', + 'split', + 'startsWith', + 'substr', + 'substring', + 'toExponential', + 'toFixed', + 'toLowerCase', + 'toPrecision', + 'toString', + 'toUpperCase', + 'trim', + 'trimEnd', + 'trimStart', + 'valueOf' +]); + +const PROJECT_CONVENTION_EXACT_NAMES = new Set(['t']); +const PROJECT_CONVENTION_PREFIXES = ['get', 'is', 'has', 'format']; + +function containsSuperCall(node) { + if (!node || typeof node !== 'object') return false; + if (Array.isArray(node)) return node.some(containsSuperCall); + + if ( + node.type === 'FunctionDeclaration' || + node.type === 'FunctionExpression' || + node.type === 'ArrowFunctionExpression' || + node.type === 'ClassDeclaration' || + node.type === 'ClassExpression' + ) { + return false; + } + + if ( + node.type === 'ExpressionStatement' && + node.expression.type === 'CallExpression' && + node.expression.callee.type === 'Super' + ) { + return true; + } + + for (const [key, value] of Object.entries(node)) { + if (key === 'parent') continue; + if (value && typeof value === 'object' && containsSuperCall(value)) return true; + } + + return false; +} + +function containsThisExpression(node) { + if (!node || typeof node !== 'object') return false; + if (node.type === 'ThisExpression') return true; + if (Array.isArray(node)) return node.some(containsThisExpression); + + if ( + node.type === 'FunctionDeclaration' || + node.type === 'FunctionExpression' || + node.type === 'ArrowFunctionExpression' || + node.type === 'ClassDeclaration' || + node.type === 'ClassExpression' + ) { + return false; + } + + for (const [key, value] of Object.entries(node)) { + if (key === 'parent') continue; + if (value && typeof value === 'object' && containsThisExpression(value)) return true; + } + + return false; +} + +function getAttachedLeadingComments(node, sourceCode) { + const attached = []; + const comments = sourceCode.getCommentsBefore(node); + let nextLine = node.loc.start.line; + let isFirst = true; + + for (let i = comments.length - 1; i >= 0; i--) { + const comment = comments[i]; + + const tokenBefore = sourceCode.getTokenBefore(comment, { includeComments: false }); + + if (tokenBefore !== null && tokenBefore.loc.end.line === comment.loc.start.line) break; + + const gap = nextLine - comment.loc.end.line; + + if (isFirst ? gap <= 2 : gap === 1) { + attached.unshift(comment); + nextLine = comment.loc.start.line; + isFirst = false; + } else { + break; + } + } + + return attached; +} + +function getFullRange(node, sourceCode) { + const commentsAfter = sourceCode.getCommentsAfter(node); + const leadingComments = getAttachedLeadingComments(node, sourceCode); + + const trailingComment = + commentsAfter.length > 0 && + commentsAfter[0].type === 'Line' && + commentsAfter[0].loc.start.line === node.loc.end.line + ? commentsAfter[0] + : null; + + const anchor = leadingComments.length > 0 ? leadingComments[0] : node; + const end = trailingComment ? trailingComment.range[1] : node.range[1]; + + const start = sourceCode.text.lastIndexOf('\n', anchor.range[0] - 1) + 1; + + return [start, end]; +} + +function getFullText(node, sourceCode) { + const [start, end] = getFullRange(node, sourceCode); + + return sourceCode.text.slice(start, end); +} + +function getSortName(id) { + if (id.type === 'ArrayPattern') { + const first = id.elements.find(Boolean); + + if (!first) return ''; + if (first.type === 'Identifier') return first.name; + if (first.type === 'AssignmentPattern' && first.left.type === 'Identifier') return first.left.name; + if (first.type === 'RestElement' && first.argument.type === 'Identifier') return first.argument.name; + + return ''; + } + + if (id.type === 'ObjectPattern') { + const [first] = id.properties; + + if (!first) return ''; + + if (first.type === 'Property') { + if (first.key.type === 'Identifier') return first.key.name; + if (first.key.type === 'Literal') return String(first.key.value); + } + + if (first.type === 'RestElement' && first.argument.type === 'Identifier') return first.argument.name; + + return ''; + } + + if (id.type === 'Identifier') return id.name; + + return ''; +} + +function getTopLevelCallee(init) { + if (!init) return null; + + if (init.type === 'CallExpression') { + if (init.callee.type === 'Identifier') return init.callee.name; + } + + if (init.type === 'MemberExpression') return getTopLevelCallee(init.object); + + return null; +} + +function getTypeOrder(id) { + if (id.type === 'ArrayPattern') return 0; + if (id.type === 'ObjectPattern') return 1; + + return 2; +} + +function compareDeclarations(declA, declB) { + const idA = declA.declarations[0]?.id; + const idB = declB.declarations[0]?.id; + + if (!idA || !idB) return 0; + + const typeOrderDiff = getTypeOrder(idA) - getTypeOrder(idB); + + if (typeOrderDiff !== 0) return typeOrderDiff; + + const nameA = getSortName(idA); + const nameB = getSortName(idB); + + return compareStrings(nameA, nameB); +} + +function getUsedInInits(node) { + const used = new Set(); + + node.declarations.forEach(declarator => { + if (declarator.init) collectUsedIdentifiers(declarator.init, used); + if (declarator.id?.typeAnnotation) collectTypeQueryRefs(declarator.id.typeAnnotation, used); + }); + + return used; +} + +function buildHasExternalVarDepMap(body, outerDeclaredNames = new Set()) { + const allDeclared = new Set(outerDeclaredNames); + const varDecls = body.filter(n => n.type === 'VariableDeclaration'); + + for (const decl of varDecls) collectDeclaredNames(decl, allDeclared); + + const map = new Map(); + + for (const decl of varDecls) { + const own = new Set(); + + collectDeclaredNames(decl, own); + let hasDep = false; + const used = getUsedInInits(decl); + + for (const name of used) { + if (allDeclared.has(name) && !own.has(name)) { + hasDep = true; + break; + } + } + + map.set(decl, hasDep); + } + + return map; +} + +function buildVarDepsMap(declarations) { + const nameToDecl = new Map(); + + for (const decl of declarations) { + const names = new Set(); + + collectDeclaredNames(decl, names); + for (const name of names) nameToDecl.set(name, decl); + } + + const deps = new Map(); + + for (const decl of declarations) { + const used = getUsedInInits(decl); + const declDeps = new Set(); + + for (const name of used) { + const dep = nameToDecl.get(name); + + if (dep && dep !== decl) declDeps.add(dep); + } + + deps.set(decl, declDeps); + } + + return deps; +} + +function getVarGroup(decl) { + if (decl.declarations.length !== 1) return 0; + const callee = getTopLevelCallee(decl.declarations[0]?.init); + + if (callee === 'useCallback') return 2; + if (callee === 'useMemo') return 1; + + return 0; +} + +function compareDeclarationsInBlock(declA, declB) { + const groupDiff = getVarGroup(declA) - getVarGroup(declB); + + if (groupDiff !== 0) return groupDiff; + + return compareDeclarations(declA, declB); +} + +function hasBlankLineBetween(nodeA, nodeB, sourceCode) { + if (nodeB.loc.start.line - nodeA.loc.end.line <= 1) return false; + + const between = sourceCode.getText().slice(nodeA.range[1], nodeB.range[0]); + + return /\n[ \t]*\n/.test(between); +} + +function isCallOrNewInit(init) { + if (!init) return false; + if (init.type === 'CallExpression' || init.type === 'NewExpression') return true; + + if (init.type === 'BinaryExpression') { + const { left, right } = init; + + return ( + left?.type === 'CallExpression' || + left?.type === 'NewExpression' || + right?.type === 'CallExpression' || + right?.type === 'NewExpression' + ); + } + + return false; +} + +function isConsoleStatement(node) { + if (node.type !== 'ExpressionStatement') return false; + const { expression } = node; + + if (expression.type !== 'CallExpression') return false; + const { callee } = expression; + + if (callee.type !== 'MemberExpression') return false; + if (callee.object.type !== 'Identifier') return false; + + return callee.object.name === 'console'; +} + +function isEligibleForSort(decl) { + if (decl.declarations.length !== 1 || !decl.declarations[0].id) return false; + const [{ init }] = decl.declarations; + + return !(init?.type === 'CallExpression' && init?.callee?.type === 'Identifier' && init?.callee?.name === 'require'); +} + +function buildGlobalSortText(mergedVarOrder, nonVarNodes, deps, sourceCode) { + const parts = []; + + for (let i = 0; i < mergedVarOrder.length; i++) { + const curr = mergedVarOrder[i]; + + if (i > 0) { + const prev = mergedVarOrder[i - 1]; + + const prevHasDep = (deps.get(prev) ?? new Set()).size > 0; + const currHasDep = (deps.get(curr) ?? new Set()).size > 0; + + const transitionToDep = currHasDep && !prevHasDep; + const currDirectlyDependsOnPrev = (deps.get(curr) ?? new Set()).has(prev); + const groupChanged = getVarGroup(curr) !== getVarGroup(prev); + const eitherMultiLine = !isOneLiner(prev) || !isOneLiner(curr); + + const outOfAlphaOrder = + !eitherMultiLine && isEligibleForSort(prev) && isEligibleForSort(curr) && compareDeclarations(prev, curr) > 0; + + if (transitionToDep || currDirectlyDependsOnPrev || groupChanged || eitherMultiLine || outOfAlphaOrder) + parts.push(''); + } + + parts.push(getFullText(curr, sourceCode)); + } + + if (nonVarNodes.length > 0) { + parts.push(''); + + for (let i = 0; i < nonVarNodes.length; i++) { + if (i > 0 && (!isOneLiner(nonVarNodes[i - 1]) || !isOneLiner(nonVarNodes[i]))) parts.push(''); + parts.push(getFullText(nonVarNodes[i], sourceCode)); + } + } + + return parts.join('\n'); +} + +function isHookLikeCall(init) { + if (!init || init.type !== 'CallExpression') return false; + + const { callee } = init; + + if (callee.type === 'Identifier') return callee.name.startsWith('use'); + if (callee.type === 'MemberExpression' && !callee.computed) { + if (callee.object.type === 'Identifier' && callee.object.name.startsWith('use')) return true; + if (callee.property?.type === 'Identifier' && callee.property.name.startsWith('use')) return true; + } + + return false; +} + +function initializerContainsAnyCall(node) { + if (!node || typeof node !== 'object') return false; + if (Array.isArray(node)) return node.some(initializerContainsAnyCall); + + if (node.type === 'CallExpression' || node.type === 'OptionalCallExpression') { + if (isHookLikeCall(node)) return false; + return true; + } + + if (node.type === 'NewExpression' || node.type === 'TaggedTemplateExpression') return true; + + if ( + node.type === 'FunctionDeclaration' || + node.type === 'FunctionExpression' || + node.type === 'ArrowFunctionExpression' || + node.type === 'ClassDeclaration' || + node.type === 'ClassExpression' + ) + return false; + + for (const [key, value] of Object.entries(node)) { + if (key === 'parent') continue; + if (value && typeof value === 'object' && initializerContainsAnyCall(value)) return true; + } + + return false; +} + +function isWorkletDirective(node) { + return ( + node.type === 'ExpressionStatement' && node.expression.type === 'Literal' && node.expression.value === 'worklet' + ); +} + +function matchesProjectConventionName(name) { + if (PROJECT_CONVENTION_EXACT_NAMES.has(name)) return true; + + for (const prefix of PROJECT_CONVENTION_PREFIXES) { + if (name.length <= prefix.length || !name.startsWith(prefix)) continue; + const next = name.charCodeAt(prefix.length); + if (next >= 65 && next <= 90) return true; + } + + return false; +} + +function isProjectConventionCallee(callee) { + if (!callee) return false; + if (callee.type === 'Identifier') return matchesProjectConventionName(callee.name); + + if (callee.type === 'MemberExpression' && !callee.computed && callee.property?.type === 'Identifier') { + if (callee.object.type !== 'Identifier') return false; + if (!/^[A-Z][A-Za-z0-9]*$/.test(callee.object.name)) return false; + return matchesProjectConventionName(callee.property.name); + } + + return false; +} + +function mergeWithIneligible(allVarDecls, eligible, eligibleSorted) { + const eligibleSet = new Set(eligible); + const queue = [...eligibleSorted]; + let qi = 0; + + return allVarDecls.map(decl => (eligibleSet.has(decl) ? queue[qi++] : decl)); +} + +function topoSortVarDecls(declarations, deps, compare) { + const originalIndex = new Map(declarations.map((d, i) => [d, i])); + const remaining = new Set(declarations); + const result = []; + + while (remaining.size > 0) { + const available = [...remaining].filter(decl => [...(deps.get(decl) ?? [])].every(dep => !remaining.has(dep))); + + if (available.length === 0) { + result.push(...[...remaining].sort((a, b) => originalIndex.get(a) - originalIndex.get(b))); + break; + } + + const minGroup = Math.min(...available.map(getVarGroup)); + + const inMinGroup = available.filter(decl => getVarGroup(decl) === minGroup); + + const noDeps = inMinGroup.filter(decl => (deps.get(decl) ?? new Set()).size === 0); + + const oneLinerNoDeps = noDeps.filter(isOneLiner); + + const candidates = oneLinerNoDeps.length > 0 ? oneLinerNoDeps : noDeps.length > 0 ? noDeps : inMinGroup; + + const next = candidates.reduce((min, d) => (compare(d, min) < 0 ? d : min)); + + result.push(next); + remaining.delete(next); + } + + return result; +} + +function isKnownPureCallee(callee) { + if (!callee) return false; + if (callee.type === 'Identifier') return KNOWN_PURE_GLOBAL_FNS.has(callee.name); + + if (callee.type === 'MemberExpression' && !callee.computed && callee.property?.type === 'Identifier') { + if (callee.object.type === 'Identifier') { + const allowed = KNOWN_PURE_NAMESPACES[callee.object.name]; + if (allowed?.has(callee.property.name)) return true; + } + + if (KNOWN_PURE_PROTOTYPE_METHODS.has(callee.property.name)) return isPureSortableInit(callee.object); + } + + return false; +} + +function isPureSortableInit(node) { + if (!node) return true; + + switch (node.type) { + case 'Literal': + case 'NullLiteral': + case 'BooleanLiteral': + case 'NumericLiteral': + case 'StringLiteral': + case 'BigIntLiteral': + case 'RegExpLiteral': + case 'Identifier': + case 'ThisExpression': + case 'ArrowFunctionExpression': + case 'FunctionExpression': + case 'ClassExpression': + case 'JSXElement': + case 'JSXFragment': + return true; + + case 'TemplateLiteral': + return node.expressions.every(isPureSortableInit); + + case 'MemberExpression': + case 'OptionalMemberExpression': + return isPureSortableInit(node.object) && (!node.computed || isPureSortableInit(node.property)); + + case 'UnaryExpression': + return node.operator !== 'delete' && isPureSortableInit(node.argument); + + case 'BinaryExpression': + case 'LogicalExpression': + return isPureSortableInit(node.left) && isPureSortableInit(node.right); + + case 'ConditionalExpression': + return isPureSortableInit(node.test) && isPureSortableInit(node.consequent) && isPureSortableInit(node.alternate); + + case 'ArrayExpression': + return node.elements.every(el => el === null || isPureSortableInit(el)); + + case 'SpreadElement': + return isPureSortableInit(node.argument); + + case 'ObjectExpression': + return node.properties.every(p => { + if (p.type === 'SpreadElement') return isPureSortableInit(p.argument); + if (p.computed) return isPureSortableInit(p.key) && isPureSortableInit(p.value); + return isPureSortableInit(p.value); + }); + + case 'CallExpression': + case 'OptionalCallExpression': + if (isHookLikeCall(node)) return true; + if (isKnownPureCallee(node.callee)) return node.arguments.every(isPureSortableInit); + if (isProjectConventionCallee(node.callee)) return node.arguments.every(isPureSortableInit); + return false; + + case 'NewExpression': + return isHookLikeCall(node); + + case 'TSAsExpression': + case 'TSNonNullExpression': + case 'TSSatisfiesExpression': + return isPureSortableInit(node.expression); + + default: + return false; + } +} + +function isPureSortableDecl(decl) { + return decl.declarations.every(d => isPureSortableInit(d.init)); +} + +function checkGlobalVarSort(body, context, sourceCode) { + if (body.length === 0) return false; + + const allVarDecls = body.filter(n => n.type === 'VariableDeclaration'); + const nonVarNodes = body.filter(n => n.type !== 'VariableDeclaration'); + + if (allVarDecls.length < 2) return false; + + const eligible = allVarDecls.filter(isEligibleForSort); + + if (eligible.length < 2) return false; + + const deps = buildVarDepsMap(allVarDecls); + + const eligibleSorted = topoSortVarDecls(eligible, deps, compareDeclarationsInBlock); + + const mergedVarOrder = mergeWithIneligible(allVarDecls, eligible, eligibleSorted); + + const alreadyInOrder = mergedVarOrder.every((n, i) => n === allVarDecls[i]); + const lastVarIdx = Math.max(...allVarDecls.map(n => body.indexOf(n))); + const firstNonVarIdx = nonVarNodes.length > 0 ? Math.min(...nonVarNodes.map(n => body.indexOf(n))) : Infinity; + + const varsBeforeNonVars = lastVarIdx < firstNonVarIdx; + + if (alreadyInOrder && varsBeforeNonVars) return false; + + if ( + !varsBeforeNonVars && + eligible.some(decl => decl.declarations.some(d => d.init && containsThisExpression(d.init))) + ) { + return false; + } + + if (!varsBeforeNonVars) { + let seenNonVar = false; + + for (const node of body) { + if (node.type !== 'VariableDeclaration') { + seenNonVar = true; + } else if (seenNonVar && node.declarations.some(d => d.init && initializerContainsAnyCall(d.init))) { + return false; + } + } + } + + if (!eligible.every(isPureSortableDecl)) return false; + + const firstViolation = !alreadyInOrder + ? allVarDecls.find((n, i) => n !== mergedVarOrder[i]) + : allVarDecls.find(n => body.indexOf(n) > firstNonVarIdx); + + context.report({ + fix(fixer) { + const [, regionEnd] = getFullRange(body[body.length - 1], sourceCode); + const [regionStart] = getFullRange(body[0], sourceCode); + + return fixer.replaceTextRange( + [regionStart, regionEnd], + buildGlobalSortText(mergedVarOrder, nonVarNodes, deps, sourceCode) + ); + }, + messageId: 'globalUnsorted', + node: firstViolation + }); + + return true; +} + +function checkSortOrder(group, context, sourceCode) { + const eligible = group.filter(isEligibleForSort); + + if (eligible.length < 2 || groupHasDependencies(eligible)) return; + + const hasOneLiner = eligible.some(isOneLiner); + const hasMultiLiner = eligible.some(d => !isOneLiner(d)); + + if (hasOneLiner && hasMultiLiner) return; + + if (!eligible.every(isPureSortableDecl)) return; + + let firstViolation = null; + + for (let i = 0; i < eligible.length - 1; i++) { + if (compareDeclarations(eligible[i], eligible[i + 1]) > 0) { + firstViolation = eligible[i + 1]; + break; + } + } + + if (!firstViolation) return; + + const sorted = [...eligible].sort(compareDeclarations); + + const merged = mergeWithIneligible(group, eligible, sorted); + + context.report({ + fix(fixer) { + const [, endRange] = getFullRange(group[group.length - 1], sourceCode); + const [startRange] = getFullRange(group[0], sourceCode); + const parts = merged.map(decl => getFullText(decl, sourceCode)); + + return fixer.replaceTextRange([startRange, endRange], parts.join('\n')); + }, + messageId: 'unsorted', + node: firstViolation + }); +} + +function checkBody(body, context, sourceCode, isBlock = false, outerDeclaredNames = new Set()) { + if (isBlock && body.length > 0 && isWorkletDirective(body[0])) { + body = body.slice(1); + } + + if (isBlock) { + let hasVarAfterNonVar = false; + let seenNonVar = false; + + for (const node of body) { + if (node.type !== 'VariableDeclaration') seenNonVar = true; + else if (seenNonVar) { + hasVarAfterNonVar = true; + break; + } + } + + if (hasVarAfterNonVar) { + const splitIndices = []; + + for (let i = 0; i < body.length; i++) { + if (body[i].type === 'IfStatement' || containsSuperCall(body[i]) || isConsoleStatement(body[i])) + splitIndices.push(i); + } + + if (splitIndices.length > 0) { + let start = 0; + const priorDeclaredNames = new Set(outerDeclaredNames); + + for (const splitIdx of splitIndices) { + if (splitIdx > start) { + const slice = body.slice(start, splitIdx); + + checkBody(slice, context, sourceCode, true, priorDeclaredNames); + + for (const node of slice) { + if (node.type === 'VariableDeclaration') collectDeclaredNames(node, priorDeclaredNames); + } + } + + start = splitIdx + 1; + } + + if (start < body.length) checkBody(body.slice(start), context, sourceCode, true, priorDeclaredNames); + + return; + } + + const hadViolation = checkGlobalVarSort(body, context, sourceCode); + + if (hadViolation) return; + + let segment = []; + const priorSegmentNames = new Set(outerDeclaredNames); + + for (const node of body) { + if (node.type === 'VariableDeclaration') { + segment.push(node); + } else { + if (segment.length > 0) { + checkBody(segment, context, sourceCode, true, priorSegmentNames); + for (const segNode of segment) collectDeclaredNames(segNode, priorSegmentNames); + segment = []; + } + } + } + + if (segment.length > 0) checkBody(segment, context, sourceCode, true, priorSegmentNames); + + return; + } + + const hadViolation = checkGlobalVarSort(body, context, sourceCode); + + if (hadViolation) return; + } + + let currentGroup = []; + const groupDeclaredNames = new Set(); + const hasExternalVarDep = buildHasExternalVarDepMap(body, outerDeclaredNames); + let prevDeclaration = null; + let prevNonVar = null; + + const flushGroup = () => { + checkSortOrder(currentGroup, context, sourceCode); + currentGroup = []; + }; + + for (const node of body) { + if (node.type !== 'VariableDeclaration') { + flushGroup(); + groupDeclaredNames.clear(); + prevDeclaration = null; + + if (isBlock && prevNonVar) { + const capturedPrevNonVar = prevNonVar; + const eitherMultiLine = !isOneLiner(prevNonVar) || !isOneLiner(node); + const hasBlanks = hasBlankLineBetween(prevNonVar, node, sourceCode); + const nextRequiresBlankAfterGuard = node.type === 'ExpressionStatement' || node.type === 'VariableDeclaration'; + + const prevIsInlineGuardClause = + capturedPrevNonVar.type === 'IfStatement' && capturedPrevNonVar.consequent.type === 'ReturnStatement'; + + if (eitherMultiLine && !hasBlanks) { + context.report({ + fix(fixer) { + const [lineStart] = getFullRange(node, sourceCode); + + return fixer.replaceTextRange([lineStart, lineStart], '\n'); + }, + messageId: 'multilineNewline', + node + }); + } else if (prevIsInlineGuardClause && nextRequiresBlankAfterGuard && !hasBlanks) { + context.report({ + fix(fixer) { + const [lineStart] = getFullRange(node, sourceCode); + + return fixer.replaceTextRange([lineStart, lineStart], '\n'); + }, + messageId: 'guardClauseNewline', + node + }); + } else if (!eitherMultiLine && hasBlanks && node.type !== 'ReturnStatement' && !prevIsInlineGuardClause) { + context.report({ + fix(fixer) { + const [end] = node.range; + const [, start] = capturedPrevNonVar.range; + + const between = sourceCode.getText().slice(start, end); + + return fixer.replaceTextRange([start, end], between.replace(/\n[ \t]*\n/, '\n')); + }, + messageId: 'unnecessaryNewline', + node + }); + } + } + + prevNonVar = node; + continue; + } + + if (prevDeclaration && hasBlankLineBetween(prevDeclaration, node, sourceCode)) { + if (isOneLiner(prevDeclaration) && isOneLiner(node)) { + if (isBlock) { + const nodeUsesPrev = [...getUsedInInits(node)].some(n => groupDeclaredNames.has(n)); + const sameGroup = getVarGroup(prevDeclaration) === getVarGroup(node); + const transitionToDep = !hasExternalVarDep.get(prevDeclaration) && hasExternalVarDep.get(node); + + if ( + !nodeUsesPrev && + !transitionToDep && + sameGroup && + sourceCode.getTokensBetween(prevDeclaration, node).length === 0 + ) { + const capturedPrev = prevDeclaration; + + context.report({ + fix(fixer) { + const [end] = node.range; + const [, start] = capturedPrev.range; + + const between = sourceCode.getText().slice(start, end); + + return fixer.replaceTextRange([start, end], between.replace(/\n[ \t]*\n/, '\n')); + }, + messageId: 'unnecessaryNewline', + node + }); + } + } else { + const nodeInit = node.declarations[0]?.init; + const prevInit = prevDeclaration.declarations[0]?.init; + + const prevIsFunc = prevInit?.type === 'ArrowFunctionExpression' || prevInit?.type === 'FunctionExpression'; + const nodeIsFunc = nodeInit?.type === 'ArrowFunctionExpression' || nodeInit?.type === 'FunctionExpression'; + const prevDeclaredNames = new Set(); + const usedInNode = getUsedInInits(node); + const usedInPrev = getUsedInInits(prevDeclaration); + + collectDeclaredNames(prevDeclaration, prevDeclaredNames); + + const sameSiblingPattern = + !prevIsFunc && + !nodeIsFunc && + isCallOrNewInit(prevInit) === isCallOrNewInit(nodeInit) && + usedInPrev.size > 0 && + usedInNode.size === usedInPrev.size && + [...usedInPrev].every(name => usedInNode.has(name)) && + ![...usedInNode].some(name => prevDeclaredNames.has(name)); + + if (sameSiblingPattern) { + const capturedPrev = prevDeclaration; + + context.report({ + fix(fixer) { + const [end] = node.range; + const [, start] = capturedPrev.range; + + const between = sourceCode.getText().slice(start, end); + + return fixer.replaceTextRange([start, end], between.replace(/\n[ \t]*\n/, '\n')); + }, + messageId: 'unnecessaryNewline', + node + }); + } + } + } + + flushGroup(); + groupDeclaredNames.clear(); + } + + let reportedMissing = false; + const usedInInits = getUsedInInits(node); + + for (const name of usedInInits) { + if (groupDeclaredNames.has(name)) { + const capturedPrev = prevDeclaration; + + context.report({ + data: { dependency: name }, + fix(fixer) { + const comments = sourceCode + .getCommentsBefore(node) + .filter(comment => comment.loc.start.line > capturedPrev.loc.end.line); + + const insertBefore = comments.length > 0 ? comments[0] : node; + + const [lineStart] = getFullRange(insertBefore, sourceCode); + + return fixer.insertTextBeforeRange([lineStart, lineStart], '\n'); + }, + messageId: 'missingNewline', + node + }); + + reportedMissing = true; + break; + } + } + + if (isBlock && !reportedMissing && prevDeclaration && !hasBlankLineBetween(prevDeclaration, node, sourceCode)) { + if (!isOneLiner(prevDeclaration) || !isOneLiner(node)) { + context.report({ + fix(fixer) { + const [lineStart] = getFullRange(node, sourceCode); + + return fixer.replaceTextRange([lineStart, lineStart], '\n'); + }, + messageId: 'multilineNewline', + node + }); + } + } + + currentGroup.push(node); + collectDeclaredNames(node, groupDeclaredNames); + prevDeclaration = node; + } + + flushGroup(); +} + +module.exports = { + create(context) { + const sourceCode = context.sourceCode; + + return { + BlockStatement(node) { + checkBody(node.body, context, sourceCode, true); + }, + Program(node) { + checkBody(node.body, context, sourceCode); + } + }; + }, + meta: { + fixable: 'code', + messages: { + globalUnsorted: + 'Variable declarations should all appear before non-variable statements and be grouped: general declarations first, then useMemo, then useCallback. Within each group, sort alphabetically (respecting dependencies).', + guardClauseNewline: 'Inline guard clause (if/return without braces) must be followed by an empty line.', + missingNewline: "Uses '{{dependency}}' declared above — add an empty line between them.", + multilineNewline: 'Multi-line declarations require an empty line between them.', + unnecessaryNewline: 'Independent one-liners — remove the empty line between them.', + unsorted: + 'Variable declarations should be ordered: array destructuring ([...]) first, object destructuring ({...}) second, then identifiers. Within each type, sort alphabetically.' + }, + schema: [], + type: 'suggestion' + } +}; diff --git a/lib/utils.js b/lib/utils.js new file mode 100644 index 0000000..62f7af0 --- /dev/null +++ b/lib/utils.js @@ -0,0 +1,272 @@ +'use strict'; + +const SKIP_KEYS = new Set(['type', 'loc', 'range', 'start', 'end', 'parent', 'tokens', 'comments']); + +function collectPatternNames(pattern, names) { + if (!pattern) return; + + switch (pattern.type) { + case 'Identifier': + names.add(pattern.name); + break; + case 'ArrayPattern': + pattern.elements.forEach(el => el && collectPatternNames(el, names)); + break; + case 'ObjectPattern': + pattern.properties.forEach(prop => { + if (prop.type === 'Property') collectPatternNames(prop.value, names); + else if (prop.type === 'RestElement') collectPatternNames(prop.argument, names); + }); + break; + case 'RestElement': + collectPatternNames(pattern.argument, names); + break; + case 'AssignmentPattern': + collectPatternNames(pattern.left, names); + break; + default: + break; + } +} + +function collectDeclaredNames(declaration, names) { + declaration.declarations.forEach(declarator => collectPatternNames(declarator.id, names)); +} + +function collectTypeQueryRefs(node, used) { + if (!node || typeof node !== 'object') return; + + if (Array.isArray(node)) { + node.forEach(child => collectTypeQueryRefs(child, used)); + + return; + } + + if (!node.type) return; + + if (node.type === 'TSTypeQuery') { + let expr = node.exprName; + + while (expr && expr.type === 'TSQualifiedName') expr = expr.left; + if (expr && expr.type === 'Identifier') used.add(expr.name); + + return; + } + + for (const key of Object.keys(node)) { + if (SKIP_KEYS.has(key)) continue; + collectTypeQueryRefs(node[key], used); + } +} + +function collectUsedIdentifiers(node, used = new Set()) { + function collectParamDefaults(param) { + if (!param) return; + + if (param.type === 'AssignmentPattern') { + collectUsedIdentifiers(param.right, used); + collectParamDefaults(param.left); + } else if (param.type === 'ObjectPattern') { + for (const prop of param.properties) { + if (prop.type === 'Property') collectParamDefaults(prop.value); + else if (prop.type === 'RestElement') collectParamDefaults(prop.argument); + } + } else if (param.type === 'ArrayPattern') { + for (const el of param.elements) { + if (el) collectParamDefaults(el); + } + } + } + + if (!node || typeof node !== 'object') return used; + + if (Array.isArray(node)) { + node.forEach(child => collectUsedIdentifiers(child, used)); + + return used; + } + + if (!node.type) return used; + + if (node.type === 'Identifier' || node.type === 'JSXIdentifier') { + used.add(node.name); + + return used; + } + + if (node.type === 'JSXMemberExpression') { + collectUsedIdentifiers(node.object, used); + + return used; + } + + if (node.type === 'MemberExpression') { + collectUsedIdentifiers(node.object, used); + if (node.computed) collectUsedIdentifiers(node.property, used); + + return used; + } + + if (node.type === 'Property') { + if (node.computed) collectUsedIdentifiers(node.key, used); + collectUsedIdentifiers(node.value, used); + + return used; + } + + if ( + node.type === 'ArrowFunctionExpression' || + node.type === 'FunctionExpression' || + node.type === 'FunctionDeclaration' + ) { + const boundNames = new Set(); + + node.params.forEach(param => collectPatternNames(param, boundNames)); + node.params.forEach(param => collectParamDefaults(param)); + + if (node.body && node.body.type === 'BlockStatement') { + node.body.body.forEach(stmt => { + if (stmt.type === 'VariableDeclaration') { + stmt.declarations.forEach(d => collectPatternNames(d.id, boundNames)); + } + }); + } + + const bodyUsed = new Set(); + + collectUsedIdentifiers(node.body, bodyUsed); + + node.params.forEach(param => { + if (param && param.typeAnnotation) collectTypeQueryRefs(param.typeAnnotation, bodyUsed); + }); + + if (node.returnType) collectTypeQueryRefs(node.returnType, bodyUsed); + + for (const name of bodyUsed) { + if (!boundNames.has(name)) used.add(name); + } + + return used; + } + + if (node.type === 'VariableDeclaration') { + node.declarations.forEach(declarator => { + if (declarator.init) collectUsedIdentifiers(declarator.init, used); + + if (declarator.id) { + collectParamDefaults(declarator.id); + if (declarator.id.typeAnnotation) collectTypeQueryRefs(declarator.id.typeAnnotation, used); + } + }); + + return used; + } + + for (const key of Object.keys(node)) { + if (SKIP_KEYS.has(key)) continue; + collectUsedIdentifiers(node[key], used); + } + + return used; +} + +function compareStrings(a, b) { + if (a < b) { + return -1; + } + + if (a > b) { + return 1; + } + + return 0; +} + +function getAllDeclaredNames(node) { + const decl = node.type === 'ExportNamedDeclaration' ? node.declaration : node; + const names = new Set(); + + if (!decl) return names; + + if (decl.type === 'FunctionDeclaration') { + if (decl.id) names.add(decl.id.name); + } else if (decl.type === 'VariableDeclaration') { + decl.declarations.forEach(declarator => collectPatternNames(declarator.id, names)); + } + + return names; +} + +function getTrailingLineComment(node, sourceCode) { + const commentsAfter = sourceCode.getCommentsAfter(node); + + if ( + commentsAfter.length > 0 && + commentsAfter[0].type === 'Line' && + commentsAfter[0].loc.start.line === node.loc.end.line + ) { + return commentsAfter[0]; + } + + return null; +} + +function groupHasDependencies(group) { + const declaredNames = new Set(); + + for (const decl of group) { + const usedInInits = new Set(); + + decl.declarations.forEach(declarator => { + if (declarator.init) collectUsedIdentifiers(declarator.init, usedInInits); + }); + + for (const name of usedInInits) { + if (declaredNames.has(name)) return true; + } + + collectDeclaredNames(decl, declaredNames); + } + + return false; +} + +function isOneLiner(node) { + return node.loc.start.line === node.loc.end.line; +} + +function topoSortAlpha(declarations, deps, compare) { + const originalIndex = new Map(declarations.map((d, i) => [d, i])); + const remaining = new Set(declarations); + const result = []; + + while (remaining.size > 0) { + const available = [...remaining].filter(decl => [...(deps.get(decl) ?? [])].every(dep => !remaining.has(dep))); + + if (available.length === 0) { + // Cycle detected — preserve original order to avoid introducing no-use-before-define violations + result.push(...[...remaining].sort((a, b) => originalIndex.get(a) - originalIndex.get(b))); + break; + } + + available.sort(compare); + result.push(available[0]); + remaining.delete(available[0]); + } + + return result; +} + +module.exports = { + SKIP_KEYS, + collectDeclaredNames, + collectPatternNames, + collectTypeQueryRefs, + collectUsedIdentifiers, + compareStrings, + getAllDeclaredNames, + getTrailingLineComment, + groupHasDependencies, + isOneLiner, + topoSortAlpha +}; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..b26214e --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3936 @@ +{ + "name": "eslint-plugin-sort-declarations", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "eslint-plugin-sort-declarations", + "version": "0.0.0", + "license": "MIT", + "devDependencies": { + "@eslint/js": "8.57.0", + "@typescript-eslint/parser": "8.22.0", + "@uphold/github-changelog-generator": "^4.0.2", + "eslint": "8.57.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.5", + "prettier": "^3.8.1", + "release-it": "^21.0.2" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "eslint": ">=8" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@inquirer/ansi": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.8.tgz", + "integrity": "sha512-WpQM+Ti6Z40EFwwt+uL2p4UabT+W179zHp6HhLVOzfbwnVn05IPO/eXIZXGNqcT1jbQ15SujNLzQ39k4QPPxBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + } + }, + "node_modules/@inquirer/checkbox": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-5.2.4.tgz", + "integrity": "sha512-oKT5RNeO9oKYizSyOxKb66IrKUsYVMQD2tnjllYW4wmSOe9WawfL3OE2p82JuvAyo+/nDIEem5P4Bm/ZIVYVew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.8", + "@inquirer/core": "^12.0.2", + "@inquirer/figures": "^2.0.9", + "@inquirer/type": "4.1.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/confirm": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-6.3.1.tgz", + "integrity": "sha512-HvnOHal39DTenOVoRpIy8Z+n4YYfNa3Qhi/P8zFqn9/d1crpmQG0DPx34rwOeOFvotgKr9Vov/14OmKR/Iwfjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^12.0.2", + "@inquirer/type": "4.1.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-12.0.2.tgz", + "integrity": "sha512-9pBhkxE14uUlnHhs8lOt7qVPtS4caRY6CjADl8COejl9Mf7w8i6Uoe3DrljCqYtmYM3Iv2Q6EmPxx/oTYbvTAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.8", + "@inquirer/figures": "^2.0.9", + "@inquirer/type": "4.1.1", + "cli-width": "^4.1.0", + "fast-wrap-ansi": "^0.2.0", + "mute-stream": "^3.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/editor": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-5.3.2.tgz", + "integrity": "sha512-7ruKO5d/wxhGHxdCQ3eyP/aBNrPTZ+Ju7ERtnAwuIsu0RSLs6kotlTRkOxHatbD6sDaEEdwsD82pzFiT9ESIyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^12.0.2", + "@inquirer/external-editor": "^3.0.5", + "@inquirer/type": "4.1.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/expand": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-5.1.4.tgz", + "integrity": "sha512-12o4aYnjWouoq0fyVhYIL+hwXAaga+kntA/wrijFmVvn2heinkdvkbEXRt1Ob/9IOJ285US5tntjfI/Ukt2YkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^12.0.2", + "@inquirer/type": "4.1.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/external-editor": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-3.0.5.tgz", + "integrity": "sha512-f3QQJRIX5ZEneBHNUIuPjmbdzHnmRFJA8r2dkcb8q+OM5Uv5KtnuAttQumnrjcBVBM3mcTX1CkmtAkU58VRZxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.2" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/figures": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-2.0.9.tgz", + "integrity": "sha512-EAWgUTGQ/Umgga51dE3B2PUHbufuXarDfg86uVgoSgNHNNQnyFKcOrQLWVqYMghuSyHh8+2HUH0Js9cTC1WAdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + } + }, + "node_modules/@inquirer/input": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-5.1.5.tgz", + "integrity": "sha512-2MkYYFD1Owt1eRBwHhsLSutysRodWasA4DVpl42KJKGmzwBTXRYAIXujisrt0UW0UohUlMjNjLfMDbw4emRbfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^12.0.2", + "@inquirer/type": "4.1.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/number": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-4.2.2.tgz", + "integrity": "sha512-tjW1gMIQsFb/9Ie11i8SpuPARBdyCVfC5XOeVugWovrdm8CpjzN+A1CBX5QwCCN2mtNmpgDDCnuLCCT94cQ5zA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^12.0.2", + "@inquirer/type": "4.1.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/password": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-5.2.1.tgz", + "integrity": "sha512-InA6nJxMCXPavPBiTKYHIsYiWz/mkIsk1rkI6pKp5w18gVawqrZOj7eSzSmPJa3j0pVxXm6RlrFP1BE9YeOixA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.8", + "@inquirer/core": "^12.0.2", + "@inquirer/type": "4.1.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/prompts": { + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-8.5.2.tgz", + "integrity": "sha512-IYR/3C/paEVVQYQvdDlFZVjRCJVYHHON0XXMH91KO9GSxs0TdKYWlUdvfQl2EfAHDxUaN3IBffkE/BDTh5nJ6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^5.2.1", + "@inquirer/confirm": "^6.1.1", + "@inquirer/editor": "^5.2.2", + "@inquirer/expand": "^5.1.1", + "@inquirer/input": "^5.1.2", + "@inquirer/number": "^4.1.1", + "@inquirer/password": "^5.1.1", + "@inquirer/rawlist": "^5.3.1", + "@inquirer/search": "^4.2.1", + "@inquirer/select": "^5.2.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/rawlist": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-5.3.4.tgz", + "integrity": "sha512-c4fDwOpQsjJDHjXdHJE1/xXH6w9/BAiyu3QKzH9Ww5Xm1q37mzyUNa20L7qdtM1ECi4e+oUEKjhK8hR37OMAMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^12.0.2", + "@inquirer/type": "4.1.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/search": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-4.3.2.tgz", + "integrity": "sha512-YIaEhWmfkdHEWOKwhF/oJar35FFTTrAdzNbJDo9lSsRT6E/obRPx9oVm4rkTMtlcJSqGYttNtLbcHQef9Qw2yw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^12.0.2", + "@inquirer/figures": "^2.0.9", + "@inquirer/type": "4.1.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/select": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-5.2.4.tgz", + "integrity": "sha512-L9ubNaJdoBsiom/AfPKq0QLHTkhhCFCtszzdVidGremrlJjPZfGlmvp+IULKIbnYjtVFWnvb4mhiEdNTZ12ugA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.8", + "@inquirer/core": "^12.0.2", + "@inquirer/figures": "^2.0.9", + "@inquirer/type": "4.1.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/type": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.1.1.tgz", + "integrity": "sha512-yJoHYrMnxIsJZCY+0Vb66Dy3he3kL3e2wOBKhoSwWWAzZAY82emlxwgprCtp6yRixvNRNq9ztfRWQYPNr3Go7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@octokit/auth-token": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", + "integrity": "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/core": { + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.8.tgz", + "integrity": "sha512-L7y8eYc+AwxGr2PWI4WFt1VG4TiJ66c26BD16mXpYIlXxG0SMigM1+m4aTSlYyBr5BlQsGAlz8uDCoZN4SEMcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/auth-token": "^6.0.0", + "@octokit/graphql": "^9.0.5", + "@octokit/request": "^10.0.16", + "@octokit/request-error": "^7.1.2", + "@octokit/types": "^18.0.0", + "before-after-hook": "^4.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/core/node_modules/@octokit/endpoint": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.5.tgz", + "integrity": "sha512-iXa654H3yFafF/ieHkukfbgWo2rmXD2ceD0ZOtrPhw1bc3FDch1d9N/TNs0FQ1/cIbwb7kspUX8jzIs8nzb9DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^18.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/core/node_modules/@octokit/graphql": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.5.tgz", + "integrity": "sha512-bt/hm03LeU6Vy7FwTrkkC9p3XGT/lBwClglMqxBSe5/q0E5CdJTXeAqEI0vlw89/LF/G6tryTIH8HirZ3prMVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/request": "^10.0.16", + "@octokit/types": "^18.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/core/node_modules/@octokit/openapi-types": { + "version": "29.0.1", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-29.0.1.tgz", + "integrity": "sha512-9qWOMFNxxLokERcms42rU0PTLqQmVs7g5E41TI4mCOxmpFayD1rfC7XxOL55cG9MBZLFlC31BrR37myMKardwg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/core/node_modules/@octokit/request": { + "version": "10.0.16", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.16.tgz", + "integrity": "sha512-A0zWGjHzISIb+9ccG8s0dq7LKO5zVpJLRICjgUb+sJxEWqn8RUHB1rD3AE51+PECvXHIxqZ1VVvs4fHTSD9nUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^11.0.5", + "@octokit/request-error": "^7.1.2", + "@octokit/types": "^18.0.0", + "content-type": "^3.0.0", + "json-with-bigint": "^3.5.12", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/core/node_modules/@octokit/request-error": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.1.2.tgz", + "integrity": "sha512-XZRuT3xZ84D3gYErI1DZvhJ33dCWVV6uzBtWkaBB4TvA/L6eOeTZodxLFVB44bBEEo3vEx7y00UfX1tBLrtLRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^18.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/core/node_modules/@octokit/types": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-18.0.0.tgz", + "integrity": "sha512-l6bAF43PNxkJp6g+W4PjoUSSkxHomXw2nOum5CTftJz1NlV3vu93NImgOYtLf6CbBUb5j+fiuzW0PPQ5JTSvZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^29.0.1" + } + }, + "node_modules/@octokit/endpoint": { + "version": "10.1.4", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.4.tgz", + "integrity": "sha512-OlYOlZIsfEVZm5HCSR8aSg02T2lbUWOsCQoPKfTXJwDzcHQBrVBGdGXb89dv2Kw2ToZaRtudp8O3ZIYoaOjKlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^14.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/graphql": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.2.2.tgz", + "integrity": "sha512-Yi8hcoqsrXGdt0yObxbebHXFOiUA+2v3n53epuOg1QUgOB6c4XzvisBNVXJSl8RYA5KrDuSL2yq9Qmqe5N0ryA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/request": "^9.2.3", + "@octokit/types": "^14.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz", + "integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-14.0.0.tgz", + "integrity": "sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^16.0.0" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": { + "version": "27.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", + "integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz", + "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^27.0.0" + } + }, + "node_modules/@octokit/plugin-request-log": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-6.0.0.tgz", + "integrity": "sha512-UkOzeEN3W91/eBq9sPZNQ7sUBvYCqYbrrD8gTbBuGtHEuycE4/awMXcYvx6sVYo7LypPhmQwwpUe4Yyu4QZN5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-17.0.0.tgz", + "integrity": "sha512-B5yCyIlOJFPqUUeiD0cnBJwWJO8lkJs5d8+ze9QDP6SvfiXSz1BF+91+0MeI1d2yxgOhU/O+CvtiZ9jSkHhFAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^16.0.0" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/openapi-types": { + "version": "27.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", + "integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz", + "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^27.0.0" + } + }, + "node_modules/@octokit/request": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.2.4.tgz", + "integrity": "sha512-q8ybdytBmxa6KogWlNa818r0k1wlqzNC+yNkcQDECHvQo8Vmstrg18JwqJHdJdUiHD2sjlwBgSm9kHkOKe2iyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^10.1.4", + "@octokit/request-error": "^6.1.8", + "@octokit/types": "^14.0.0", + "fast-content-type-parse": "^2.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/request-error": { + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.8.tgz", + "integrity": "sha512-WEi/R0Jmq+IJKydWlKDmryPcmdYSVjL3ekaiEL1L9eo1sUnqMJ+grqmC9cjk7CA7+b2/T397tO5d8YLOH3qYpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^14.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/rest": { + "version": "22.0.1", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-22.0.1.tgz", + "integrity": "sha512-Jzbhzl3CEexhnivb1iQ0KJ7s5vvjMWcmRtq5aUsKmKDrRW6z3r84ngmiFKFvpZjpiU/9/S6ITPFRpn5s/3uQJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/core": "^7.0.6", + "@octokit/plugin-paginate-rest": "^14.0.0", + "@octokit/plugin-request-log": "^6.0.0", + "@octokit/plugin-rest-endpoint-methods": "^17.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/types": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz", + "integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^25.1.0" + } + }, + "node_modules/@phun-ky/typeof": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@phun-ky/typeof/-/typeof-2.0.3.tgz", + "integrity": "sha512-oeQJs1aa8Ghke8JIK9yuq/+KjMiaYeDZ38jx7MhkXncXlUKjqQ3wEm2X3qCKyjo+ZZofZj+WsEEiqkTtRuE2xQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.9.0 || >=22.0.0", + "npm": ">=10.8.2" + }, + "funding": { + "url": "https://github.com/phun-ky/typeof?sponsor=1" + } + }, + "node_modules/@pkgr/core": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.3.6.tgz", + "integrity": "sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@types/parse-path": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/parse-path/-/parse-path-7.0.3.tgz", + "integrity": "sha512-LriObC2+KYZD3FzCrgWGv/qufdUy4eXrxcLgQMfYXgPbLIecKIsVBaQgUPmxSSLcjmYbDTQbMgr6qr6l/eb7Bg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.22.0.tgz", + "integrity": "sha512-MqtmbdNEdoNxTPzpWiWnqNac54h8JDAmkWtJExBVVnSrSmi9z+sZUt0LfKqk9rjqmKOIeRhO4fHHJ1nQIjduIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.22.0", + "@typescript-eslint/types": "8.22.0", + "@typescript-eslint/typescript-estree": "8.22.0", + "@typescript-eslint/visitor-keys": "8.22.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.22.0.tgz", + "integrity": "sha512-/lwVV0UYgkj7wPSw0o8URy6YI64QmcOdwHuGuxWIYznO6d45ER0wXUbksr9pYdViAofpUCNJx/tAzNukgvaaiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.22.0", + "@typescript-eslint/visitor-keys": "8.22.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.22.0.tgz", + "integrity": "sha512-0S4M4baNzp612zwpD4YOieP3VowOARgK2EkN/GBn95hpyF8E2fbMT55sRHWBq+Huaqk3b3XK+rxxlM8sPgGM6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.22.0.tgz", + "integrity": "sha512-SJX99NAS2ugGOzpyhMza/tX+zDwjvwAtQFLsBo3GQxiGcvaKlqGBkmZ+Y1IdiSi9h4Q0Lr5ey+Cp9CGWNY/F/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.22.0", + "@typescript-eslint/visitor-keys": "8.22.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.22.0.tgz", + "integrity": "sha512-AWpYAXnUgvLNabGTy3uBylkgZoosva/miNd1I8Bz3SjotmQPbVqhO4Cczo8AsZ44XVErEBPr/CRSgaj8sG7g0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.22.0", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.4.0.tgz", + "integrity": "sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@uphold/github-changelog-generator": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@uphold/github-changelog-generator/-/github-changelog-generator-4.0.2.tgz", + "integrity": "sha512-ImM/V4ha6axscURhN/wjFUl0AZoz3IjQFzMdmRZ7qPWGeTsGyetzIO2WMJbVH75TfyXqmgvPCowPGfklzuK70g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/graphql": "^8.2.1", + "commander": "^8.3.0", + "ini": "^2.0.0", + "look-it-up": "^2.1.0", + "moment": "^2.29.1" + }, + "bin": { + "github-changelog-generator": "bin/github-changelog-generator.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-9.0.0.tgz", + "integrity": "sha512-TQf59BsZnytt8GdJKLPfUZ54g/iaUL2OWDSFCCvMOhsHduDQxO8xC4PNeyIkVcA5KwL2phPSv0douC0fgWzmnA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "retry": "0.13.1" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/basic-ftp": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz", + "integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/before-after-hook": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz", + "integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/brace-expansion": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/c12": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/c12/-/c12-3.3.4.tgz", + "integrity": "sha512-cM0ApFQSBXuourJejzwv/AuPRvAxordTyParRVcHjjtXirtkzM0uK2L9TTn9s0cXZbG7E55jCivRQzoxYmRAlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^5.0.0", + "confbox": "^0.2.4", + "defu": "^6.1.6", + "dotenv": "^17.3.1", + "exsolve": "^1.0.8", + "giget": "^3.2.0", + "jiti": "^2.6.1", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "perfect-debounce": "^2.1.0", + "pkg-types": "^2.3.0", + "rc9": "^3.0.1" + }, + "peerDependencies": { + "magicast": "*" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chardet": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.2.0.tgz", + "integrity": "sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==", + "dev": true, + "license": "MIT" + }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-3.4.0.tgz", + "integrity": "sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/confbox": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/content-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-3.0.0.tgz", + "integrity": "sha512-AIi5H6p0xk5uknXcN3/rmhP8jgp69OfSe/JuKiQAFprJ7UGw7mwj7m4XcmDzlrnJDG+cGpphAINGdU3g3g7kDw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-8.0.0.tgz", + "integrity": "sha512-6UHfyCux51b8PTGDgveqtz1tvphBku5DrMKKJbFAZAJOI2zsjDpDoYE1+QGj7FOMS4BdTFNJsJiR3zEB0xH0yQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/default-browser": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.1.tgz", + "integrity": "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/degenerator": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-7.0.1.tgz", + "integrity": "sha512-ABErK0IefDSyHjlPH7WUEenIAX2rPPnrDcDM+TS3z3+zu9TfyKKi07BQM+8rmxpdE2y1v5fjjdoAS/x4D2U60w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "quickjs-wasi": "^2.2.0" + } + }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "dev": true, + "license": "MIT" + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/eslint": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.5.6", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.6.tgz", + "integrity": "sha512-ifetmTcxWfz+4qRW3pH/ujdTq2jQIj59AxJMIN26K5avYgU8dxycUETQonWiW+wPrYXA0j3Try0l1CnwVQtDqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.1", + "synckit": "^0.11.13" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eta": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eta/-/eta-4.6.0.tgz", + "integrity": "sha512-lW6is4T1NFOYnmqGZIfvixqj7A7sSvScF+DN8EK6K58xI5MZ5UvYe0GjopxOXQtZvUn4eDdVuZ8XSoYWTMEKwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/bgub/eta?sponsor=1" + } + }, + "node_modules/exsolve": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.1.1.tgz", + "integrity": "sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-content-type-parse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-2.0.1.tgz", + "integrity": "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-string-truncated-width": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", + "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-string-width": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz", + "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-string-truncated-width": "^3.0.2" + } + }, + "node_modules/fast-wrap-ansi": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.2.tgz", + "integrity": "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-string-width": "^3.0.2" + } + }, + "node_modules/fastq": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.3.tgz", + "integrity": "sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-uri": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-8.0.1.tgz", + "integrity": "sha512-/5N/P4Lrh0p/mDwlDRi7Y1+P2o/OyzZI3l6Iz1Ov6XXwwm1y3RlZLuo3gVgML99djrEDtV980bBxSuOeHLk8ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.3.1", + "data-uri-to-buffer": "8.0.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/giget": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/giget/-/giget-3.3.1.tgz", + "integrity": "sha512-r+mvuDjrjMpsdw46Kmeydb8bdHm7wOKw8wNBtTndkjbPjgAp5oUJUxRE76wZFknxIPokfWvep2qSXK37aXE6zg==", + "dev": true, + "license": "MIT", + "bin": { + "giget": "dist/cli.mjs" + } + }, + "node_modules/git-up": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/git-up/-/git-up-8.1.1.tgz", + "integrity": "sha512-FDenSF3fVqBYSaJoYy1KSc2wosx0gCvKP+c+PRBht7cAaiCeQlBtfBDX9vgnNOHmdePlSFITVcn4pFfcgNvx3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-ssh": "^1.4.0", + "parse-url": "^9.2.0" + } + }, + "node_modules/git-url-parse": { + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-16.1.0.tgz", + "integrity": "sha512-cPLz4HuK86wClEW7iDdeAKcCVlWXmrLpb2L+G9goW0Z1dtpNS6BXXSOckUTlJT/LDQViE1QZKstNORzHsLnobw==", + "dev": true, + "license": "MIT", + "dependencies": { + "git-up": "^8.1.0" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/http-proxy-agent": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-9.1.0.tgz", + "integrity": "sha512-2NxoveTT58mjYT4n3RPTEfCZGLMbidoO8XEieXfpSYxu+PQJ1qpx4ypwH6N+uF9twBPIvRRgvkvW5HUTYWENig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "9.0.0", + "debug": "^4.3.4", + "proxy-agent-negotiate": "1.1.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/https-proxy-agent": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-9.1.0.tgz", + "integrity": "sha512-ag87y7cJJ9/3+GxFr8Oy4O5faDsGRGnBGsJj/YjOSsSx/5eadKLYTMPlzuR6obgoCDDm0abAAZitXXQkMOPSpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "9.0.0", + "debug": "^4.3.4", + "proxy-agent-negotiate": "1.1.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/ip-address": { + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.0.tgz", + "integrity": "sha512-BGFsyJd5mpXp3rK6jIdADLNgpJUK1jnjzvYF8lK+VyDab9JAmqN0YOKDdP17HlgKb2+ehPgDc8EtnRLbGCAMhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-in-ssh": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz", + "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-ssh": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.4.1.tgz", + "integrity": "sha512-JNeu1wQsHjyHgn9NcWTaXq6zWSR6hqE0++zhfZlkFBbScNkyvxCdeV8sRkSBaeLKxmbpR21brail63ACNxJ0Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "protocols": "^2.0.1" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/issue-parser": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-7.0.2.tgz", + "integrity": "sha512-7atWPjhGEIX3JEtMrOYd8TKzboYlq+5sNbdl9POiLYOI14G5HZiQbZP0Xj5EZdrufQVXfJlpTV0hys0CuxwxZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.capitalize": "^4.2.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.uniqby": "^4.7.0" + }, + "engines": { + "node": "^18.17 || >=20.6.1" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-with-bigint": { + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/json-with-bigint/-/json-with-bigint-3.5.12.tgz", + "integrity": "sha512-uwbF/wSSuOgC7qqlq27Xp5B6a2MHVug3t0idZdTqu0JnlFvgJuH7ju+KAk/J06C7GfhoYy2gnb9wz2INqcne7w==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.capitalize": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz", + "integrity": "sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.uniqby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", + "integrity": "sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-7.0.1.tgz", + "integrity": "sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/look-it-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/look-it-up/-/look-it-up-2.1.0.tgz", + "integrity": "sha512-nMoGWW2HurtuJf6XAL56FWTDCWLOTSsanrgwOyaR5Y4e3zfG5N/0cU5xWZSEU3tBxhQugRbV1xL9jb+ug7yZww==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/macos-release": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-3.5.1.tgz", + "integrity": "sha512-Lci/1in+elqZ589PXnfP/iwZXpwQifTM94WJRQwG2tZSdfY7NfB/aUaTARHrohWCgHlXoabeaeXRDOnF5X9JQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mute-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-3.0.0.tgz", + "integrity": "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/netmask": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.1.1.tgz", + "integrity": "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/new-github-release-url": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/new-github-release-url/-/new-github-release-url-2.0.0.tgz", + "integrity": "sha512-NHDDGYudnvRutt/VhKFlX26IotXe1w0cmkDm6JGquh5bz/bDTw0LufSmH/GxTjEdpHEO+bVKFTwdrcGa/9XlKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^2.5.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/new-github-release-url/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ohash": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.12.tgz", + "integrity": "sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw==", + "dev": true, + "license": "MIT" + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/open/-/open-11.0.0.tgz", + "integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.4.0", + "define-lazy-prop": "^3.0.0", + "is-in-ssh": "^1.0.0", + "is-inside-container": "^1.0.0", + "powershell-utils": "^0.1.0", + "wsl-utils": "^0.3.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ora": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-9.4.1.tgz", + "integrity": "sha512-6VlU9MLXbjVQD04AZCMX28hVtA5bUoadvUqO76MUCVA0ilwJbMiHsITRPfyVm6p/BC0Av/BXMujx39WCe1LEqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.6.2", + "cli-cursor": "^5.0.0", + "cli-spinners": "^3.2.0", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.1.0", + "log-symbols": "^7.0.1", + "stdin-discarder": "^0.3.2", + "string-width": "^8.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/os-name": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/os-name/-/os-name-7.0.0.tgz", + "integrity": "sha512-/HfRU/lPPr4T2VigM+cvM3cU77es+XF4OEAa4aE5zpdvrxHGD2NmH0AFIWpMNAb+CsZL45rlcIO49Re0ZcRseg==", + "dev": true, + "license": "MIT", + "dependencies": { + "macos-release": "^3.4.0", + "windows-release": "^7.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pac-proxy-agent": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-9.1.0.tgz", + "integrity": "sha512-1aU+1mpj3DrQPfo3gh+3Gap3G5x+axnMx1P/y0ZF2ch7kb2meyOCAH8K2k9d27ROsTE7TnAerzxqF9aon2jqnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "9.0.0", + "debug": "^4.3.4", + "get-uri": "8.0.1", + "http-proxy-agent": "9.1.0", + "https-proxy-agent": "9.1.0", + "pac-resolver": "9.0.1", + "quickjs-wasi": "^2.2.0", + "socks-proxy-agent": "10.1.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/pac-resolver": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-9.0.1.tgz", + "integrity": "sha512-lJbS008tmkj08VhoM8Hzuv/VE5tK9MS0OIQ/7+s0lIF+BYhiQWFYzkSpML7lXs9iBu2jfmzBTLzhe9n6BX+dYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "degenerator": "7.0.1", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "quickjs-wasi": "^2.2.0" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-path": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-7.1.0.tgz", + "integrity": "sha512-EuCycjZtfPcjWk7KTksnJ5xPMvWGA/6i4zrLYhRG0hGvC3GPU/jGUj3Cy+ZR0v30duV3e23R95T1lE2+lsndSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "protocols": "^2.0.0" + } + }, + "node_modules/parse-url": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-9.2.0.tgz", + "integrity": "sha512-bCgsFI+GeGWPAvAiUv63ZorMeif3/U0zaXABGJbOWt5OH2KCaPHF6S+0ok4aqM9RuIPGyZdx9tR9l13PsW4AYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/parse-path": "^7.0.0", + "parse-path": "^7.0.0" + }, + "engines": { + "node": ">=14.13.0" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", + "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-types": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.2.tgz", + "integrity": "sha512-v0sVXzj7oPGysr543YYZLYbcJNJsKikSsp/fFzoxQ12ewY3ZZr7oCPC8y7OlmxfYB3QPvriXmuPD8KZggE1vqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.3.0", + "exsolve": "^1.1.1", + "pathe": "^2.0.3" + } + }, + "node_modules/pkg-types/node_modules/confbox": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.3.1.tgz", + "integrity": "sha512-cKUSoKa8YxFZZSmraVi7onONx3amu77ngK3kGpsYHDH7drPwCRkQE1RYMPlLRrMtnciRj274XNRxcHxnKmDSnA==", + "dev": true, + "license": "MIT" + }, + "node_modules/powershell-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz", + "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.9.6", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz", + "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz", + "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/protocols": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/protocols/-/protocols-2.0.2.tgz", + "integrity": "sha512-hHVTzba3wboROl0/aWRRG9dMytgH6ow//STBZh43l/wQgmMhYhOFi0EHWAPtoCz9IAUymsyP0TSBHkhgMEGNnQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/proxy-agent": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-8.0.2.tgz", + "integrity": "sha512-idLLRewuemWd7GH/BDJzGiB0dWGfT2SQs3jy6NtZtGWU9uPTTSdeC1/cdbqLwgzhfv027daGFuXX426e2Eg20A==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "9.0.0", + "debug": "^4.3.4", + "http-proxy-agent": "9.1.0", + "https-proxy-agent": "9.1.0", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "9.1.0", + "proxy-from-env": "^2.0.0", + "socks-proxy-agent": "10.1.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/proxy-agent-negotiate": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-agent-negotiate/-/proxy-agent-negotiate-1.1.0.tgz", + "integrity": "sha512-N8IBcM3UgCVzz2L2Lqv8DVntDnnC8/hiV4nEDUPkqq72TPUgYWjQc+bdZlBPZK9LzPAvOY//gAt0S0DApoOXWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "kerberos": "^2.0.0" + }, + "peerDependenciesMeta": { + "kerberos": { + "optional": true + } + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/quickjs-wasi": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/quickjs-wasi/-/quickjs-wasi-2.2.0.tgz", + "integrity": "sha512-zQxXmQMrEoD3S+jQdYsloq4qAuaxKFHZj6hHqOYGwB2iQZH+q9e/lf5zQPXCKOk0WJuAjzRFbO4KwHIp2D05Iw==", + "dev": true, + "license": "MIT" + }, + "node_modules/rc9": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-3.1.0.tgz", + "integrity": "sha512-ufjkNVzbRHKcCOmTahZkmVsyc3W+MSk3jY03m+a7tGHkIsdVMG9l10/3HvFbWkkKzY5VFp3pkRsIo/UYgmFL7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "defu": "^6.1.7", + "destr": "^2.0.5" + } + }, + "node_modules/readdirp": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz", + "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/release-it": { + "version": "21.0.2", + "resolved": "https://registry.npmjs.org/release-it/-/release-it-21.0.2.tgz", + "integrity": "sha512-QwyDnWiQNB4d8Hc2b5FLMHcsKz9S2O1dMNnPB7w+0knsL5r8+qbnznDA1X7Bw5D863+Ud7eRHc71Cfth5Tlrgg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/webpro" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/webpro" + } + ], + "license": "MIT", + "dependencies": { + "@inquirer/prompts": "8.5.2", + "@octokit/rest": "22.0.1", + "@phun-ky/typeof": "2.0.3", + "async-retry": "1.3.3", + "c12": "3.3.4", + "ci-info": "^4.4.0", + "defu": "^6.1.7", + "eta": "4.6.0", + "git-url-parse": "16.1.0", + "issue-parser": "7.0.2", + "lodash.merge": "4.6.2", + "mime-types": "3.0.2", + "new-github-release-url": "2.0.0", + "open": "11.0.0", + "ora": "9.4.1", + "os-name": "7.0.0", + "proxy-agent": "8.0.2", + "semver": "7.8.5", + "tinyglobby": "0.2.17", + "undici": "7.29.0", + "url-join": "5.0.0", + "wildcard-match": "5.1.4" + }, + "bin": { + "release-it": "bin/release-it.js" + }, + "engines": { + "node": "^22.21.0 || >=24.0.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.10", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.10.tgz", + "integrity": "sha512-e0VyvkVTwVYViNovRkZ9aodhxVlyoMn7eJhVUPxZ+eK9P/7CBkxvvsBOHqFPEH416726W8tLXXXjKwqgTErrCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.1.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-10.1.0.tgz", + "integrity": "sha512-WlMj/67cEJ6MDI1OcsnjuYKDNDoyPCCYZ249kuuXPiMDw9F8PXkVaQ7YWu3siTydfQ/4BEZcvGzu+aYvz7dDCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "9.0.0", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stdin-discarder": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.3.2.tgz", + "integrity": "sha512-eCPu1qRxPVkl5605OTWF8Wz40b4Mf45NY5LQmVPQ599knfs5QhASUm9GbJ5BDMDOXgrnh0wyEdvzmL//YMlw0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz", + "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/synckit": { + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.13.tgz", + "integrity": "sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.3.6" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz", + "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/universal-user-agent": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", + "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", + "dev": true, + "license": "ISC" + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-join": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-5.0.0.tgz", + "integrity": "sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wildcard-match": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/wildcard-match/-/wildcard-match-5.1.4.tgz", + "integrity": "sha512-wldeCaczs8XXq7hj+5d/F38JE2r7EXgb6WQDM84RVwxy81T/sxB5e9+uZLK9Q9oNz1mlvjut+QtvgaOQFPVq/g==", + "dev": true, + "license": "ISC" + }, + "node_modules/windows-release": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-7.1.1.tgz", + "integrity": "sha512-0GBwC9WmR8Bm3WYiz3FC391054BsFHZ2gzBVdYj9uj5eIVYzbn/YPYCYW9SWdh9vwnLuzpn1UGwJKiMG4F236w==", + "dev": true, + "license": "MIT", + "dependencies": { + "powershell-utils": "^0.2.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/windows-release/node_modules/powershell-utils": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.2.1.tgz", + "integrity": "sha512-C+y9x90UElAddDZmV4qOx9W53B61PO7cIqWz2dQsWlwswuq4mr8NEwytdGKboYbQlGZ3awrkTeNvcZiZNHnQ8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/wsl-utils": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.3.1.tgz", + "integrity": "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0", + "powershell-utils": "^0.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.2.0.tgz", + "integrity": "sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..ae8c127 --- /dev/null +++ b/package.json @@ -0,0 +1,54 @@ +{ + "name": "eslint-plugin-sort-declarations", + "version": "0.0.0", + "description": "An ESLint plugin to sort module-level and block-level declarations.", + "keywords": [ + "eslint", + "eslintplugin", + "eslint-plugin", + "sort", + "declarations", + "variables", + "exports", + "module" + ], + "author": "Uphold", + "type": "commonjs", + "main": "lib/index.js", + "exports": { + ".": { + "require": "./lib/index.js", + "import": "./lib/index.js" + }, + "./package.json": "./package.json" + }, + "files": [ + "lib" + ], + "repository": "github:uphold/eslint-plugin-sort-declarations", + "publishConfig": { + "access": "public" + }, + "scripts": { + "lint": "ESLINT_USE_FLAT_CONFIG=true eslint .", + "release": "release-it", + "test": "node --test tests/sort-*.js" + }, + "devDependencies": { + "@eslint/js": "8.57.0", + "@typescript-eslint/parser": "8.22.0", + "@uphold/github-changelog-generator": "^4.0.2", + "eslint": "8.57.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.5", + "prettier": "^3.8.1", + "release-it": "^21.0.2" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "eslint": ">=8" + }, + "license": "MIT" +} diff --git a/tests/sort-export-declarations.js b/tests/sort-export-declarations.js new file mode 100644 index 0000000..6ec2e81 --- /dev/null +++ b/tests/sort-export-declarations.js @@ -0,0 +1,87 @@ +'use strict'; + +const { RuleTester } = require('eslint'); +const rule = require('../lib/rules/sort-export-declarations'); + +const esmTester = new RuleTester({ parserOptions: { ecmaVersion: 2020, sourceType: 'module' } }); + +esmTester.run('sort-export-declarations', rule, { + invalid: [ + // two local re-exports out of alphabetical order → fix sorts them + { + code: "export * from './zoo';\nexport * from './alpha';", + errors: [{ messageId: 'sortOrder' }], + output: "export * from './alpha';\nexport * from './zoo';" + }, + // three local re-exports with multiple out-of-order → fix sorts all in one pass + { + code: "export * from './zoo';\nexport * from './alpha';\nexport * from './mid';", + errors: [{ messageId: 'sortOrder' }], + output: "export * from './alpha';\nexport * from './mid';\nexport * from './zoo';" + }, + // non-local (group 2) appearing AFTER local (group 3) → fix reorders + { + code: "export * from './local';\nexport * from 'react';", + errors: [{ messageId: 'sortOrder' }], + output: "export * from 'react';\n\nexport * from './local';" + }, + // local binding (group 1) appearing AFTER non-local (group 2) → fix reorders + { + code: "const bar = 1, baz = 2;\nexport * from 'react';\nexport { foo } from 'lodash';\nexport { bar, baz };", + errors: [{ messageId: 'sortOrder' }], + output: "const bar = 1, baz = 2;\nexport { bar, baz };\n\nexport { foo } from 'lodash';\nexport * from 'react';" + }, + // all three groups wrong: local first, then local-binding, then non-local → fix puts them in correct group order + { + code: "const bar = 1, baz = 2;\nexport * from './local';\nexport { bar, baz };\nexport * from 'react';", + errors: [{ messageId: 'sortOrder' }], + output: "const bar = 1, baz = 2;\nexport { bar, baz };\n\nexport * from 'react';\n\nexport * from './local';" + }, + // two different groups with no blank line between them → missingBlankLine fix adds \n + { + code: "export * from 'react';\nexport * from './local';", + errors: [{ messageId: 'missingBlankLine' }], + output: "export * from 'react';\n\nexport * from './local';" + }, + // two same-group exports with an extra blank line between them → extraBlankLine fix removes it + { + code: "export * from './alpha';\n\nexport * from './zoo';", + errors: [{ messageId: 'extraBlankLine' }], + output: "export * from './alpha';\nexport * from './zoo';" + }, + // uppercase sorts before lowercase — Garden (G=71) before ball (b=98) + { + code: 'const ball = 1, Garden = 2;\nexport { ball };\nexport { Garden };', + errors: [{ messageId: 'sortOrder' }], + output: 'const ball = 1, Garden = 2;\nexport { Garden };\nexport { ball };' + } + ], + valid: [ + // single export — no sorting required + { code: "export * from './foo';" }, + // uppercase before lowercase is already correct — no error + { code: 'const Garden = 2, ball = 1;\nexport { Garden };\nexport { ball };' }, + // two sorted local export * — no error + { code: "export * from './alpha';\nexport * from './zoo';" }, + // many sorted local export * — no error + { code: "export * from './alpha';\nexport * from './beta';\nexport * from './zoo';" }, + // only group 2 (non-local), sorted alphabetically — no error + { code: "export * from 'lodash';\nexport * from 'react';" }, + // only group 1 (local binding), sorted alphabetically — no error + { + code: 'const bar = 1, foo = 2;\nexport { bar };\nexport { foo };' + }, + // all three groups in correct order with blank lines between them — no error + { + code: "const baz = 1;\nexport { baz };\n\nexport * from 'react';\n\nexport * from './local';" + }, + // ../ path before ./ path (natural alphabetical, .. < . in ASCII) — no error + { code: "export * from '../bar';\nexport * from './foo';" }, + // non-export nodes between export regions — each region treated independently + { + code: "export * from './alpha';\nexport * from './zoo';\nconst x = 1;\nexport * from 'a';\nexport * from 'b';" + }, + // just two exports in correct order without blank lines (same group) — no error + { code: "export * from './alpha';\nexport * from './zoo';" } + ] +}); diff --git a/tests/sort-module-declarations.js b/tests/sort-module-declarations.js new file mode 100644 index 0000000..8631581 --- /dev/null +++ b/tests/sort-module-declarations.js @@ -0,0 +1,747 @@ +'use strict'; + +const { RuleTester } = require('eslint'); +const rule = require('../lib/rules/sort-module-declarations'); + +const esmTester = new RuleTester({ parserOptions: { ecmaVersion: 2020, sourceType: 'module' } }); +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2020 } }); + +ruleTester.run('sort-module-declarations', rule, { + invalid: [ + // two single-line functions out of alphabetical order + { + code: 'function zoo() {}\n\nfunction alpha() {}', + errors: [{ messageId: 'sortOrder' }], + output: 'function alpha() {}\nfunction zoo() {}' + }, + // three single-line functions out of order — all reordered in one fix + { + code: 'function zoo() {}\n\nfunction alpha() {}\n\nfunction mid() {}', + errors: [{ messageId: 'sortOrder' }], + output: 'function alpha() {}\nfunction mid() {}\nfunction zoo() {}' + }, + // function (group 5) before SCREAMING_SNAKE const (group 1) — group ordering violated + { + code: 'function zoo() {}\n\nconst A = 1;', + errors: [{ messageId: 'sortOrder' }], + output: 'const A = 1;\n\nfunction zoo() {}' + }, + // two single-line functions out of alphabetical order + { + code: 'function b() {}\n\nfunction a() {}', + errors: [{ messageId: 'sortOrder' }], + output: 'function a() {}\nfunction b() {}' + }, + // two same-group one-liner consts in wrong order — no blank line needed between them + { + code: 'const b = 1;\nconst a = 2;', + errors: [{ messageId: 'sortOrder' }], + output: 'const a = 2;\nconst b = 1;' + }, + // uppercase sorts before lowercase — Garden (G=71) before ball (b=98) + { + code: 'const ball = 1;\nconst Garden = 2;', + errors: [{ messageId: 'sortOrder' }], + output: 'const Garden = 2;\nconst ball = 1;' + }, + // multi-line functions in wrong order — body text swapped correctly + { + code: ['function b() {', ' return 1;', '}', '', 'function a() {', ' return 2;', '}'].join('\n'), + errors: [{ messageId: 'sortOrder' }], + output: ['function a() {', ' return 2;', '}', '', 'function b() {', ' return 1;', '}'].join('\n') + }, + // three multi-line functions out of order — all reordered in one fix + { + code: [ + 'function c() {', + ' return 1;', + '}', + '', + 'function a() {', + ' return 2;', + '}', + '', + 'function b() {', + ' return 3;', + '}' + ].join('\n'), + errors: [{ messageId: 'sortOrder' }], + output: [ + 'function a() {', + ' return 2;', + '}', + '', + 'function b() {', + ' return 3;', + '}', + '', + 'function c() {', + ' return 1;', + '}' + ].join('\n') + }, + // sort violation + missing blank line — fixed in one pass + { + code: ['function b() {', ' return 1;', '}', 'function a() {', ' return 2;', '}'].join('\n'), + errors: [{ messageId: 'sortOrder' }], + output: ['function a() {', ' return 2;', '}', '', 'function b() {', ' return 1;', '}'].join('\n') + }, + // SCREAMING_SNAKE after regular const — sort + blank line fixed in one pass + { + code: ['const alpha = 1;', 'const BETA = 2;'].join('\n'), + errors: [{ messageId: 'sortOrder' }], + output: ['const BETA = 2;', '', 'const alpha = 1;'].join('\n') + }, + // const (group 3) before function (group 5) but missing blank line — sort + blank line fixed in one pass + { + code: ['function a() {', ' return 1;', '}', 'const b = 1;'].join('\n'), + errors: [{ messageId: 'sortOrder' }], + output: ['const b = 1;', '', 'function a() {', ' return 1;', '}'].join('\n') + }, + // missing blank line between const (group 3) and function (group 5) — different groups require blank line + { + code: ['const a = 1;', 'function b() {', ' return 1;', '}'].join('\n'), + errors: [{ messageId: 'missingBlankLine' }], + output: ['const a = 1;', '', 'function b() {', ' return 1;', '}'].join('\n') + }, + // missing blank line between two adjacent multi-line functions in same group + { + code: ['function a() {', ' return 1;', '}', 'function b() {', ' return 2;', '}'].join('\n'), + errors: [{ messageId: 'missingBlankLine' }], + output: ['function a() {', ' return 1;', '}', '', 'function b() {', ' return 2;', '}'].join('\n') + }, + // missing blank line between multi-line functions with dependency — dependency check fires for non-one-liners + { + code: ['function a() {', ' return 1;', '}', 'function b() {', ' return a();', '}'].join('\n'), + errors: [{ messageId: 'missingBlankLine' }], + output: ['function a() {', ' return 1;', '}', '', 'function b() {', ' return a();', '}'].join('\n') + }, + // let (group 2) after regular const (group 3) — let must come before const + { + code: 'const a = 1;\n\nlet b = 2;', + errors: [{ messageId: 'sortOrder' }], + output: 'let b = 2;\n\nconst a = 1;' + }, + // var (group 2) after regular const (group 3) — var must come before const + { + code: 'const a = 1;\n\nvar b = 2;', + errors: [{ messageId: 'sortOrder' }], + output: 'var b = 2;\n\nconst a = 1;' + }, + // PropTypes object (group 6) before function (group 5) — functions come before PropTypes + { + code: 'const propTypes = { foo: PropTypes.string };\n\nfunction render() {}', + errors: [{ messageId: 'sortOrder' }], + output: 'function render() {}\n\nconst propTypes = { foo: PropTypes.string };' + }, + // dependency ordering — b uses a but b appears first, a must come first + { + code: ['function b() {', ' return a();', '}', '', 'function a() {', ' return 1;', '}'].join('\n'), + errors: [{ messageId: 'sortOrder' }], + output: ['function a() {', ' return 1;', '}', '', 'function b() {', ' return a();', '}'].join('\n') + }, + // callDeps — result directly calls foo, foo (arrow fn, group 5) must precede result (const call, group 4) + { + code: 'const result = foo();\n\nconst foo = () => {};', + errors: [{ messageId: 'sortOrder' }], + output: 'const foo = () => {};\n\nconst result = foo();' + }, + // const call (group 4) before plain const (group 3) — plain const must come first + { + code: 'const result = foo();\nconst value = 42;', + errors: [{ messageId: 'sortOrder' }], + output: 'const value = 42;\n\nconst result = foo();' + }, + // missing blank line between plain const (group 3) and const call (group 4) + { + code: 'const value = 42;\nconst result = foo();', + errors: [{ messageId: 'missingBlankLine' }], + output: 'const value = 42;\n\nconst result = foo();' + }, + // arrow function (group 5) before SCREAMING_SNAKE const (group 1) — group ordering violated + { + code: 'const foo = () => {};\n\nconst LIMIT = 10;', + errors: [{ messageId: 'sortOrder' }], + output: 'const LIMIT = 10;\n\nconst foo = () => {};' + }, + // function expression (group 5) before regular const (group 3) — const must come before function expression + { + code: ['const handler = function() {', ' return 1;', '};', '', 'const value = 42;'].join('\n'), + errors: [{ messageId: 'sortOrder' }], + output: ['const value = 42;', '', 'const handler = function() {', ' return 1;', '};'].join('\n') + }, + // const new expression (group 4) before regular const (group 3) — const must come first + { + code: ['const store = new Store(', ' option', ');', '', 'const value = 42;'].join('\n'), + errors: [{ messageId: 'sortOrder' }], + output: ['const value = 42;', '', 'const store = new Store(', ' option', ');'].join('\n') + }, + // const call expression (group 4) before regular const (group 3) — const must come first + { + code: ['const store = createStore(', ' reducer', ');', '', 'const value = 42;'].join('\n'), + errors: [{ messageId: 'sortOrder' }], + output: ['const value = 42;', '', 'const store = createStore(', ' reducer', ');'].join('\n') + }, + // const member call expression (group 4) before regular const (group 3) — const must come first + { + code: ['const store = obj.create(', ' option', ');', '', 'const value = 42;'].join('\n'), + errors: [{ messageId: 'sortOrder' }], + output: ['const value = 42;', '', 'const store = obj.create(', ' option', ');'].join('\n') + }, + // plain Identifier const (order 2) before ArrayPattern const (order 0) — ArrayPattern sorts first, no blank line between one-liner same-group consts + { + code: 'const z = 1;\n\nconst [a] = arr;', + errors: [{ messageId: 'sortOrder' }], + output: 'const [a] = arr;\nconst z = 1;' + }, + // plain Identifier const (order 2) before ObjectPattern const (order 1) — ObjectPattern sorts first, no blank line between one-liner same-group consts + { + code: 'const z = 1;\n\nconst { x } = obj;', + errors: [{ messageId: 'sortOrder' }], + output: 'const { x } = obj;\nconst z = 1;' + }, + // leading comment moves with its declaration — comment above zoo moves when zoo is reordered + { + code: ['// comment for zoo', 'function zoo() {}', '', 'function alpha() {}'].join('\n'), + errors: [{ messageId: 'sortOrder' }], + output: ['function alpha() {}', '// comment for zoo', 'function zoo() {}'].join('\n') + }, + // trailing inline comment moves with its declaration — inline comment after zoo moves during reorder + { + code: ['function zoo() {} // zoo comment', '', 'function alpha() {}'].join('\n'), + errors: [{ messageId: 'sortOrder' }], + output: ['function alpha() {}', 'function zoo() {} // zoo comment'].join('\n') + }, + // graphql file — mutation (group 6) before query (group 5), query must come first + { + code: ['const MUTATION = gql`mutation { foo }`;', '', 'const QUERY = gql`query { bar }`;'].join('\n'), + errors: [{ messageId: 'sortOrder' }], + output: ['const QUERY = gql`query { bar }`;', '', 'const MUTATION = gql`mutation { foo }`;'].join('\n') + }, + // graphql file — PropTypes (group 9) before query gql (group 5) — query must come first + { + code: ['const propTypes = { foo: PropTypes.string };', '', 'const QUERY = gql`query { bar }`;'].join('\n'), + errors: [{ messageId: 'sortOrder' }], + output: ['const QUERY = gql`query { bar }`;', '', 'const propTypes = { foo: PropTypes.string };'].join('\n') + }, + // graphql file — non-GQL plain const (group 2) after gql fragment (group 4) — const must come first + { + code: ['const FRAGMENT = gql`fragment F on T { id }`;', '', 'const value = 1;'].join('\n'), + errors: [{ messageId: 'sortOrder' }], + output: ['const value = 1;', '', 'const FRAGMENT = gql`fragment F on T { id }`;'].join('\n') + }, + // graphql file — non-GQL const call (group 3) after gql fragment (group 4) — const call must come first + { + code: ['const FRAGMENT = gql`fragment F on T { id }`;', '', 'const Context = Object.freeze({ A: "a" });'].join( + '\n' + ), + errors: [{ messageId: 'sortOrder' }], + output: ['const Context = Object.freeze({ A: "a" });', '', 'const FRAGMENT = gql`fragment F on T { id }`;'].join( + '\n' + ) + }, + // only the out-of-order region is flagged — second region (after require) is already sorted + { + code: 'const zoo = 1;\nconst alpha = 2;\nconst x = require("x");\nconst c = 3;\nconst d = 4;', + errors: [{ messageId: 'sortOrder' }], + output: 'const alpha = 2;\nconst zoo = 1;\nconst x = require("x");\nconst c = 3;\nconst d = 4;' + }, + // console.log breaks the sortable region — vars on each side sort within their segment but never across + { + code: 'const z = 1;\nconst m = 2;\nconsole.log(m);\nconst b = 3;\nconst a = 4;', + errors: [{ messageId: 'sortOrder' }, { messageId: 'sortOrder' }], + output: 'const m = 2;\nconst z = 1;\nconsole.log(m);\nconst a = 4;\nconst b = 3;' + }, + // uppercase sorts before lowercase — Z (90) < a (97) in code-point order, so Zoo sorts before alpha + { + code: 'function Zoo() {}\n\nfunction alpha() {}', + errors: [{ messageId: 'extraBlankLine' }], + output: 'function Zoo() {}\nfunction alpha() {}' + }, + // let one-liners with no blank lines between them, followed by blank-line-separated consts — fix must not insert blank lines between the let declarations + { + code: ['let z = 1;', 'let a = 2;', '', 'const b = 3;'].join('\n'), + errors: [{ messageId: 'sortOrder' }], + output: ['let a = 2;', 'let z = 1;', '', 'const b = 3;'].join('\n') + }, + // SCREAMING_SNAKE one-liners with dependency — sorted with blank line between dependent pair + { + code: [ + 'const INDICATOR_HEIGHT = 48;', + 'const SCALE_MULTIPLIER = 2;', + '', + 'const INDICATOR_CENTER = INDICATOR_HEIGHT / 2;' + ].join('\n'), + errors: [{ messageId: 'sortOrder' }], + output: [ + 'const INDICATOR_HEIGHT = 48;', + '', + 'const INDICATOR_CENTER = INDICATOR_HEIGHT / 2;', + 'const SCALE_MULTIPLIER = 2;' + ].join('\n') + }, + // function (group 5) before independent const (group 3) — const must come first even when another const depends on this function + { + code: [ + 'const getResources = resources => resources;', + '', + 'const languageCodes = { EN: "en" };', + '', + 'const defaultOptions = { r: getResources({}) };' + ].join('\n'), + errors: [{ messageId: 'sortOrder' }], + output: [ + 'const languageCodes = { EN: "en" };', + '', + 'const getResources = resources => resources;', + '', + 'const defaultOptions = { r: getResources({}) };' + ].join('\n') + }, + // extra blank line between two same-group one-liner consts — should be removed + { + code: 'const a = 1;\n\nconst b = 2;', + errors: [{ messageId: 'extraBlankLine' }], + output: 'const a = 1;\nconst b = 2;' + }, + // extra blank line between two SCREAMING_SNAKE one-liner consts — should be removed + { + code: 'const A = 1;\n\nconst B = 2;', + errors: [{ messageId: 'extraBlankLine' }], + output: 'const A = 1;\nconst B = 2;' + }, + // extra blank line between two one-liner functions — should be removed + { + code: 'function alpha() {}\n\nfunction zoo() {}', + errors: [{ messageId: 'extraBlankLine' }], + output: 'function alpha() {}\nfunction zoo() {}' + }, + // missing blank line between dependent one-liner consts — dependency takes priority over the one-liner check + { + code: 'const A = 1;\nconst B = A + 1;', + errors: [{ messageId: 'missingBlankLine' }], + output: 'const A = 1;\n\nconst B = A + 1;' + }, + // multiple extra blank lines between one-liner consts — all collapsed to a single newline + { + code: 'const a = 1;\n\n\nconst b = 2;', + errors: [{ messageId: 'extraBlankLine' }], + output: 'const a = 1;\nconst b = 2;' + }, + // missing blank line before one-liner with non-adjacent in-group dependency — blank required to keep sort-variable-declarations in agreement + { + code: 'const a = 1;\nconst b = 2;\nconst c = a + 1;', + errors: [{ messageId: 'missingBlankLine' }], + output: 'const a = 1;\nconst b = 2;\n\nconst c = a + 1;' + }, + // missing blank line before one-liner depending on two non-adjacent group members — still requires the blank + { + code: 'const a = 1;\nconst b = 2;\nconst c = 3;\nconst d = a + b;', + errors: [{ messageId: 'missingBlankLine' }], + output: 'const a = 1;\nconst b = 2;\nconst c = 3;\n\nconst d = a + b;' + }, + // sandwiched one-liner function cluster missing blank line — blank required between cluster items + { + code: [ + 'function aa() {', + ' return 1;', + '}', + '', + 'function ab() {}', + 'function ac() {}', + '', + 'function ba() {', + ' return 2;', + '}' + ].join('\n'), + errors: [{ messageId: 'missingBlankLine' }], + output: [ + 'function aa() {', + ' return 1;', + '}', + '', + 'function ab() {}', + '', + 'function ac() {}', + '', + 'function ba() {', + ' return 2;', + '}' + ].join('\n') + } + ], + valid: [ + // single const declaration — nothing to sort + { code: 'const foo = 1;' }, + // single function declaration — nothing to sort + { code: 'function foo() {}' }, + // two SCREAMING_SNAKE consts in correct alphabetical order — same group, no blank line needed + { code: 'const A = 1;\nconst B = 2;' }, + // SCREAMING_SNAKE group then regular const group with blank line separator + { code: 'const A = 1;\nconst B = 2;\n\nconst a = 3;\nconst b = 4;' }, + // full group ordering — SCREAMING_SNAKE, regular const, functions — all correctly sorted + { + code: [ + 'const A = 1;', + 'const B = 2;', + '', + 'const a = 3;', + 'const b = 4;', + '', + 'function foo() {}', + 'function zoo() {}' + ].join('\n') + }, + // two multi-line functions in correct alphabetical order + { + code: ['function alpha() {', ' return 1;', '}', '', 'function zoo() {', ' return 2;', '}'].join('\n') + }, + // function zoo uses alpha — dependency forces alpha before zoo despite reverse alphabetical + { + code: ['function zoo() {', ' return 1;', '}', '', 'function alpha() {', ' return zoo();', '}'].join('\n') + }, + // require calls are not sorted — alphabetical order is irrelevant for requires + { code: 'const a = require("a");\nconst z = require("z");' }, + // require calls are not sorted — reverse order also valid + { code: 'const z = require("z");\nconst a = require("a");' }, + // two single-line consts in alphabetical order — no blank line needed between same-group one-liners + { code: 'const a = 1;\nconst b = 2;' }, + // uppercase before lowercase is already correct — no error + { code: 'const Garden = 2;\nconst ball = 1;' }, + // two single-line function declarations in alphabetical order — no blank line needed between one-liner functions + { code: 'function alpha() {}\nfunction zoo() {}' }, + // two one-liner arrow functions in alphabetical order — no blank line needed + { code: 'const setHidden = () => f();\nconst setVisible = () => f();' }, + // two multi-line functions in alphabetical order with blank line + { + code: ['function a() {', ' return 1;', '}', '', 'function b() {', ' return 2;', '}'].join('\n') + }, + // require breaks sortable region — each region sorted independently, aaa < zoo in second region + { + code: 'const foo = 1;\nrequire("side-effect");\nconst aaa = 2;\nconst zoo = 3;' + }, + // console.log breaks sortable region — declarations on each side stay in their own segment, console.log never moves + { code: 'const a = 1;\nconsole.log(a);\nconst b = 2;' }, + // console.error and other console methods also break sortable regions + { code: 'const a = 1;\nconsole.error("oops");\nconst b = 2;\nconst c = 3;' }, + // SCREAMING_SNAKE (group 1) before functions (group 5) with blank line; no blank between one-liner functions + { + code: ['const A = 1;', '', 'function foo() {}', 'function zoo() {}'].join('\n') + }, + // let (group 2) before regular const (group 3) — correct group ordering + { code: 'let b = 2;\n\nconst a = 1;' }, + // var (group 2) before regular const (group 3) — correct group ordering + { code: 'var b = 2;\n\nconst a = 1;' }, + // SCREAMING_SNAKE (1), let (2), regular const (3) all in correct order + { code: 'const A = 1;\n\nlet b = 2;\n\nconst c = 3;' }, + // function (group 5) before PropTypes object (group 6) — correct group ordering + { code: 'function render() {}\n\nconst propTypes = { foo: PropTypes.string };' }, + // arrow function (group 5) after regular const (group 3) + { code: 'const a = 1;\n\nconst foo = () => {};' }, + // function expression (group 5) after regular const (group 3) + { + code: ['const value = 42;', '', 'const handler = function() {', ' return 1;', '};'].join('\n') + }, + // const new expression (group 4), comes after regular const (group 3) + { + code: ['const value = 42;', '', 'const store = new Store(', ' option', ');'].join('\n') + }, + // const call expression (group 4), comes after regular const (group 3) + { + code: ['const value = 42;', '', 'const store = createStore(', ' reducer', ');'].join('\n') + }, + // const member call expression (group 4), comes after regular const (group 3) + { + code: ['const value = 42;', '', 'const store = obj.create(', ' option', ');'].join('\n') + }, + // ArrayPattern (sort order 0) before plain Identifier (sort order 2) within same group — no blank line between one-liners + { code: 'const [a] = arr;\nconst z = 1;' }, + // ObjectPattern (sort order 1) before plain Identifier (sort order 2) within same group — no blank line between one-liners + { code: 'const { x } = obj;\nconst z = 1;' }, + // ArrayPattern (0) before ObjectPattern (1) before plain Identifier (2) within same group — no blank lines between one-liners + { code: 'const [a] = arr;\nconst { x } = obj;\nconst z = 1;' }, + // ArrayPattern getName uses first element — [a] sorts as 'a', [b] sorts as 'b' + { code: 'const [a] = arr;\nconst [b] = other;' }, + // ObjectPattern getName uses first property key — { a } sorts as 'a', { z } sorts as 'z' + { code: 'const { a } = obj1;\nconst { z } = obj2;' }, + // callDeps — foo (arrow fn, group 5) before result (const call, group 4) due to direct call dependency + { code: 'const foo = () => {};\n\nconst result = foo();' }, + // plain const (group 3) before const call (group 4) — correct group ordering + { code: 'const value = 42;\n\nconst result = foo();' }, + // binary expression wrapping a call is still const call — month = getMonth() + 1 must not sort before date/year + { + code: [ + 'const today = new Date();', + '', + 'const date = today.getDate();', + 'const month = today.getMonth() + 1;', + 'const year = today.getFullYear();' + ].join('\n') + }, + // two same-group const call one-liners in alphabetical order — no blank line needed + { code: 'const margin = createMixin(def);\nconst padding = createMixin(def);' }, + // const new expression (group 4) after plain const (group 3) — correct ordering + { code: 'const value = 42;\n\nconst store = new Store(option);' }, + // independent const (group 3) before function (group 5) before dependent const (group 3) — group rules apply to dependencies + { + code: [ + 'const languageCodes = { EN: "en" };', + '', + 'const getResources = resources => resources;', + '', + 'const defaultOptions = { r: getResources({}) };' + ].join('\n') + }, + // multi-line functions with dependency and blank line — missingBlankLine satisfied, extraBlankLine does not fire + { + code: ['function a() {', ' return 1;', '}', '', 'function b() {', ' return a();', '}'].join('\n') + }, + // one-liner dependent consts with blank line — dependency takes priority, blank line is required and extraBlankLine does not fire + { code: 'const A = 1;\n\nconst B = A + 1;' }, + // two sortable regions separated by require — each region is sorted independently + { + code: 'const a = 1;\nconst b = 2;\nconst x = require("x");\nconst c = 3;\nconst d = 4;' + }, + // require calls excluded from sorting regardless of order + { code: 'const z = require("z");\nconst a = require("a");\nconst m = require("m");' }, + // graphql file — fragment (group 4) before query (group 5) — correct gql group ordering + { + code: ['const FRAGMENT = gql`fragment F on T { id }`;', '', 'const QUERY = gql`query { bar }`;'].join('\n') + }, + // graphql file — query (group 5) before mutation (group 6) — correct gql group ordering + { + code: ['const QUERY = gql`query { bar }`;', '', 'const MUTATION = gql`mutation { foo }`;'].join('\n') + }, + // graphql file — full order: non-gql const, fragment, query, mutation, function, PropTypes + { + code: [ + 'const value = 1;', + '', + 'const FRAGMENT = gql`fragment F on T { id }`;', + '', + 'const QUERY = gql`query { bar }`;', + '', + 'const MUTATION = gql`mutation { foo }`;', + '', + 'function render() {}', + '', + 'const propTypes = { foo: PropTypes.string };' + ].join('\n') + }, + // sandwiched one-liner function cluster with blank lines — blank lines present, no error + { + code: [ + 'function aa() {', + ' return 1;', + '}', + '', + 'function ab() {}', + '', + 'function ac() {}', + '', + 'function ba() {', + ' return 2;', + '}' + ].join('\n') + }, + // one-liner clusters on each side of a multi-liner — one-liners joined within each cluster + { + code: [ + 'function aa() {}', + 'function ab() {}', + '', + 'function ba() {', + ' return 1;', + '}', + '', + 'function bb() {}', + 'function bc() {}' + ].join('\n') + }, + // single cluster at start, multi-liners at end — one-liners joined + { + code: [ + 'function aa() {}', + 'function ab() {}', + '', + 'function ba() {', + ' return 1;', + '}', + '', + 'function bb() {', + ' return 2;', + '}' + ].join('\n') + }, + // single cluster at end — one-liners joined + { + code: [ + 'function aa() {', + ' return 1;', + '}', + '', + 'function ab() {', + ' return 2;', + '}', + '', + 'function ba() {}', + 'function bb() {}' + ].join('\n') + }, + // all one-liners with dep-separated clusters — dep blank lines required, non-dep one-liners joined + { + code: ['const a = 1;', 'const b = 2;', '', 'const c = a + b;', 'const d = 4;', 'const e = 5;'].join('\n') + }, + // blank line before one-liner with non-adjacent in-group dependency — required so sort-variable-declarations stays in agreement + { + code: ['const a = 1;', 'const b = 2;', '', 'const c = a + 1;'].join('\n') + }, + // non-adjacent dep followed by independent one-liners — subsequent independent nodes cluster without extra blanks + { + code: ['const a = 1;', 'const b = 2;', '', 'const c = a + 1;', 'const d = 4;'].join('\n') + }, + // same-source deps on a separated node — dependents of `today` cluster together without extra blanks between them + { + code: [ + 'const today = new Date();', + '', + 'const date = today.getDate();', + 'const month = today.getMonth() + 1;', + 'const year = today.getFullYear();' + ].join('\n') + }, + // vite.config.js regression — non-adjacent in-group dep must not oscillate with sort-variable-declarations + { + code: [ + "const developmentServerHost = 'uphold.test';", + 'const developmentServerPort = 3000;', + "const environment = process.env.NODE_ENV || 'development';", + "const extensions = ['.web.jsx', '.jsx', '.web.js', '.js'];", + '', + "const isDevelopment = !['production', 'sandbox'].includes(environment);", + "const isLocalDevelopment = process.env.LOCAL_DEVELOPMENT === 'true';" + ].join('\n') + } + ] +}); + +esmTester.run('sort-module-declarations (ESM)', rule, { + invalid: [ + // exported single-line functions in wrong alphabetical order + { + code: 'export function zoo() {}\n\nexport function alpha() {}', + errors: [{ messageId: 'sortOrder' }], + output: 'export function alpha() {}\nexport function zoo() {}' + }, + // exported multi-line functions in wrong order + { + code: ['export function zoo() {', ' return 1;', '}', '', 'export function alpha() {', ' return 2;', '}'].join( + '\n' + ), + errors: [{ messageId: 'sortOrder' }], + output: ['export function alpha() {', ' return 2;', '}', '', 'export function zoo() {', ' return 1;', '}'].join( + '\n' + ) + }, + // exported same-group one-liner consts in wrong alphabetical order + { + code: 'export const zoo = 1;\nexport const alpha = 2;', + errors: [{ messageId: 'sortOrder' }], + output: 'export const alpha = 2;\nexport const zoo = 1;' + }, + // ESM — missing blank line between SCREAMING_SNAKE const (group 1) and function (group 5) + { + code: 'export const A = 1;\nexport function foo() {}', + errors: [{ messageId: 'missingBlankLine' }], + output: 'export const A = 1;\n\nexport function foo() {}' + }, + // ESM — regular const (group 3) before SCREAMING_SNAKE (group 1) — group ordering violated + { + code: 'export const b = 2;\n\nexport const A = 1;', + errors: [{ messageId: 'sortOrder' }], + output: 'export const A = 1;\n\nexport const b = 2;' + }, + // ESM — dependency ordering — b uses a but b appears first, a must come first + { + code: ['export function b() {', ' return a();', '}', '', 'export function a() {', ' return 1;', '}'].join('\n'), + errors: [{ messageId: 'sortOrder' }], + output: ['export function a() {', ' return 1;', '}', '', 'export function b() {', ' return a();', '}'].join( + '\n' + ) + }, + // ESM — extra blank line between exported one-liner functions — should be removed + { + code: 'export function alpha() {}\n\nexport function zoo() {}', + errors: [{ messageId: 'extraBlankLine' }], + output: 'export function alpha() {}\nexport function zoo() {}' + }, + // ESM — extra blank line between exported one-liner consts — should be removed + { + code: 'export const alpha = 1;\n\nexport const zoo = 2;', + errors: [{ messageId: 'extraBlankLine' }], + output: 'export const alpha = 1;\nexport const zoo = 2;' + }, + // ESM — non-exported followed by exported same-group one-liner with no blank — blank required + { + code: 'const A = 1;\nexport const B = 2;', + errors: [{ messageId: 'missingBlankLine' }], + output: 'const A = 1;\n\nexport const B = 2;' + }, + // ESM — exported followed by non-exported same-group one-liner with no blank — blank required + { + code: 'export const A = 1;\nconst B = 2;', + errors: [{ messageId: 'missingBlankLine' }], + output: 'export const A = 1;\n\nconst B = 2;' + }, + // ESM — extra blank line between exported one-liner functions with shared non-adjacent dep — should be removed + { + code: 'export const formatDate = date => date;\n\nexport const formatDateTime = date => date;', + errors: [{ messageId: 'extraBlankLine' }], + output: 'export const formatDate = date => date;\nexport const formatDateTime = date => date;' + } + ], + valid: [ + // exported functions in correct alphabetical order — no blank line between one-liners + { code: 'export function alpha() {}\nexport function zoo() {}' }, + // exported consts in alphabetical order — same group, no blank line needed + { code: 'export const alpha = 1;\nexport const zoo = 2;' }, + // multi-line exported functions in correct alphabetical order + { + code: ['export function alpha() {', ' return 1;', '}', '', 'export function zoo() {', ' return 2;', '}'].join( + '\n' + ) + }, + // ESM — SCREAMING_SNAKE (group 1) before regular const (group 3) — correct group ordering + { code: 'export const A = 1;\n\nexport const b = 2;' }, + // ESM — SCREAMING_SNAKE (group 1) before function (group 5) with blank line + { code: 'export const A = 1;\n\nexport function foo() {}' }, + // ESM — dependency ordering — a before b because b uses a + { + code: ['export function a() {', ' return 1;', '}', '', 'export function b() {', ' return a();', '}'].join('\n') + }, + // ESM — non-exported followed by exported same-group one-liner with blank line — blank is required due to mixed export status + { code: 'const A = 1;\n\nexport const B = 2;' }, + // ESM — exported followed by non-exported same-group one-liner with blank line — blank is required due to mixed export status + { code: 'export const A = 1;\n\nconst B = 2;' } + ] +}); + +const tsRuleTester = new RuleTester({ + parser: require.resolve('@typescript-eslint/parser'), + parserOptions: { ecmaVersion: 2020 } +}); + +tsRuleTester.run('sort-module-declarations (typescript)', rule, { + invalid: [ + // regression: `typeof y` in a module-scope variable id type annotation is a real dependency + { + code: 'const x: typeof y = null;\nconst y = 1;', + errors: [{ messageId: 'sortOrder' }], + output: 'const y = 1;\n\nconst x: typeof y = null;' + } + ], + valid: [ + // `typeof y` in id type annotation — x correctly ordered after y + { code: 'const y = 1;\n\nconst x: typeof y = null;' } + ] +}); diff --git a/tests/sort-variable-declarations.js b/tests/sort-variable-declarations.js new file mode 100644 index 0000000..82c4cc7 --- /dev/null +++ b/tests/sort-variable-declarations.js @@ -0,0 +1,779 @@ +'use strict'; + +const { RuleTester } = require('eslint'); +const rule = require('../lib/rules/sort-variable-declarations'); + +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2020 } }); + +ruleTester.run('sort-variable-declarations', rule, { + invalid: [ + // sort violations + { + code: 'const z = 1;\nconst a = 2;', + errors: [{ messageId: 'unsorted' }], + output: 'const a = 2;\nconst z = 1;' + }, + // uppercase sorts before lowercase — Garden (G=71) before ball1 (b=98) + { + code: 'const ball1 = {};\nconst Garden = {};', + errors: [{ messageId: 'unsorted' }], + output: 'const Garden = {};\nconst ball1 = {};' + }, + { + code: 'const b = 1;\nconst a = 2;\nconst c = 3;', + errors: [{ messageId: 'unsorted' }], + output: 'const a = 2;\nconst b = 1;\nconst c = 3;' + }, + { + code: 'const foo = 1;\nconst [bar] = [];', + errors: [{ messageId: 'unsorted' }], + output: 'const [bar] = [];\nconst foo = 1;' + }, + { + code: 'const foo = 1;\nconst { bar } = {};', + errors: [{ messageId: 'unsorted' }], + output: 'const { bar } = {};\nconst foo = 1;' + }, + { + code: 'const { zoo } = {};\nconst { alpha } = {};', + errors: [{ messageId: 'unsorted' }], + output: 'const { alpha } = {};\nconst { zoo } = {};' + }, + { + code: 'const [zoo] = [];\nconst [alpha] = [];', + errors: [{ messageId: 'unsorted' }], + output: 'const [alpha] = [];\nconst [zoo] = [];' + }, + { + code: 'const { zoo } = {};\nconst [alpha] = [];', + errors: [{ messageId: 'unsorted' }], + output: 'const [alpha] = [];\nconst { zoo } = {};' + }, + { + code: 'function foo() {\n const z = 1;\n const a = 2;\n}', + errors: [{ messageId: 'globalUnsorted' }], + output: 'function foo() {\n const a = 2;\n const z = 1;\n}' + }, + { + code: 'const z = 1;\nconst a = 2;\nconst m = 3;', + errors: [{ messageId: 'unsorted' }], + output: 'const a = 2;\nconst m = 3;\nconst z = 1;' + }, + { + code: 'const a = require("a");\nconst z = 1;\nconst b = 2;', + errors: [{ messageId: 'unsorted' }], + output: 'const a = require("a");\nconst b = 2;\nconst z = 1;' + }, + // sort violation with leading comment — comment moves with its declaration + { + code: '// comment\nconst z = 1;\nconst a = 2;', + errors: [{ messageId: 'unsorted' }], + output: 'const a = 2;\n// comment\nconst z = 1;' + }, + // sort violation with comment separated from declaration by a blank line — comment moves with declaration + { + code: 'function f() {\n const z = 1;\n\n // comment\n\n const a = 2;\n}', + errors: [{ messageId: 'globalUnsorted' }], + output: 'function f() {\n // comment\n\n const a = 2;\n const z = 1;\n}' + }, + // sort violation where dependency requires blank line — only missingNewline reported + { + code: 'const b = 1;\nconst a = b + 1;', + errors: [{ data: { dependency: 'b' }, messageId: 'missingNewline' }], + output: 'const b = 1;\n\nconst a = b + 1;' + }, + // missingNewline violations + { + code: 'const a = 1;\nconst b = a + 1;', + errors: [{ data: { dependency: 'a' }, messageId: 'missingNewline' }], + output: 'const a = 1;\n\nconst b = a + 1;' + }, + { + code: 'const x = foo();\nconst y = x.value;\nconst z = y + 1;', + errors: [ + { data: { dependency: 'x' }, messageId: 'missingNewline' }, + { data: { dependency: 'y' }, messageId: 'missingNewline' } + ], + output: 'const x = foo();\n\nconst y = x.value;\n\nconst z = y + 1;' + }, + { + code: 'const a = 1;\nconst b = 2;\nconst c = a + b;', + errors: [{ data: { dependency: 'a' }, messageId: 'missingNewline' }], + output: 'const a = 1;\nconst b = 2;\n\nconst c = a + b;' + }, + { + code: 'const a = getValue();\n// comment\nconst b = a + 1;', + errors: [{ data: { dependency: 'a' }, messageId: 'missingNewline' }], + output: 'const a = getValue();\n\n// comment\nconst b = a + 1;' + }, + { + code: 'const a = getValue();\nconst b = getValue();\nconst c = getValue();\nconst d = a + 1;', + errors: [{ data: { dependency: 'a' }, messageId: 'missingNewline' }], + output: 'const a = getValue();\nconst b = getValue();\nconst c = getValue();\n\nconst d = a + 1;' + }, + // unnecessaryNewline violations + { + code: 'const a = getValue();\n\nconst b = getValue();', + errors: [{ messageId: 'unnecessaryNewline' }], + output: 'const a = getValue();\nconst b = getValue();' + }, + { + code: 'const { x } = getX();\n\nconst { y } = getX();', + errors: [{ messageId: 'unnecessaryNewline' }], + output: 'const { x } = getX();\nconst { y } = getX();' + }, + // unnecessaryNewline — siblings sharing deps and both being MemberExpression (same module-level group) + { + code: 'const a = obj.x;\n\nconst b = obj.y;', + errors: [{ messageId: 'unnecessaryNewline' }], + output: 'const a = obj.x;\nconst b = obj.y;' + }, + // globalUnsorted violations + { + code: 'function f() {\n const z = 1;\n foo();\n const a = 2;\n}', + errors: [{ messageId: 'globalUnsorted' }], + output: 'function f() {\n const a = 2;\n const z = 1;\n\n foo();\n}' + }, + { + code: 'function f() {\n const a = 1;\n foo();\n const b = 2;\n}', + errors: [{ messageId: 'globalUnsorted' }], + output: 'function f() {\n const a = 1;\n const b = 2;\n\n foo();\n}' + }, + { + code: 'function f() {\n const a = 1;\n useEffect(() => {}, [a]);\n const b = a + 1;\n}', + errors: [{ messageId: 'globalUnsorted' }], + output: 'function f() {\n const a = 1;\n\n const b = a + 1;\n\n useEffect(() => {}, [a]);\n}' + }, + { + code: 'function f() {\n const a = useMemo(() => 1, []);\n foo();\n const b = useCallback(() => {}, []);\n}', + errors: [{ messageId: 'globalUnsorted' }], + output: + 'function f() {\n const a = useMemo(() => 1, []);\n\n const b = useCallback(() => {}, []);\n\n foo();\n}' + }, + { + code: 'function f() {\n const c = useCallback(() => {}, []);\n const m = useMemo(() => 1, []);\n useEffect(() => {}, []);\n const x = 1;\n}', + errors: [{ messageId: 'globalUnsorted' }], + output: + 'function f() {\n const x = 1;\n\n const m = useMemo(() => 1, []);\n\n const c = useCallback(() => {}, []);\n\n useEffect(() => {}, []);\n}' + }, + { + code: 'function f() {\n const form = useForm();\n useEffect(() => {}, []);\n const x = 1;\n}', + errors: [{ messageId: 'globalUnsorted' }], + output: 'function f() {\n const form = useForm();\n const x = 1;\n\n useEffect(() => {}, []);\n}' + }, + { + code: 'function f() {\n const m = useMemo(() => v, [v]);\n useEffect(() => {}, []);\n const v = useCustomHook();\n}', + errors: [{ messageId: 'globalUnsorted' }], + output: + 'function f() {\n const v = useCustomHook();\n\n const m = useMemo(() => v, [v]);\n\n useEffect(() => {}, []);\n}' + }, + // globalUnsorted — independent identifier must not be displaced by a same-group ObjectPattern whose dep just became available + { + code: 'function f() {\n const { a } = useA();\n const { b } = useB(a);\n const x = useX();\n foo();\n}', + errors: [{ messageId: 'globalUnsorted' }], + output: 'function f() {\n const { a } = useA();\n const x = useX();\n\n const { b } = useB(a);\n\n foo();\n}' + }, + // formatting fixes only — non-hook call watch(['amount']) blocks global reorder, but formatting violations still fire + { + code: "function f() {\n const {\n control,\n watch\n } = useFormContext();\n const { t } = useTranslation();\n\n const [amount] = watch(['amount']);\n\n const msg = t('x');\n\n foo();\n}", + errors: [{ messageId: 'multilineNewline' }, { messageId: 'unnecessaryNewline' }], + output: + "function f() {\n const {\n control,\n watch\n } = useFormContext();\n\n const { t } = useTranslation();\n\n const [amount] = watch(['amount']);\n const msg = t('x');\n\n foo();\n}" + }, + // globalUnsorted — wrong group order across blank line (useCallback before useMemo) + { + code: 'function f() {\n const cb = useCallback(() => {}, []);\n\n const m = useMemo(() => 1, []);\n}', + errors: [{ messageId: 'globalUnsorted' }], + output: 'function f() {\n const m = useMemo(() => 1, []);\n\n const cb = useCallback(() => {}, []);\n}' + }, + // globalUnsorted — wrong alphabetical order across blank line (sorts and removes unnecessary blank line) + { + code: 'function f() {\n const z = 1;\n\n const a = 2;\n}', + errors: [{ messageId: 'globalUnsorted' }], + output: 'function f() {\n const a = 2;\n const z = 1;\n}' + }, + // globalUnsorted — topo sort produces alpha order for one-liners (defaultValue before onClick/onSubmit, no blank line needed) + { + code: 'function f() {\n const { b, c } = useBC();\n const { d, e } = useDE(\n b\n );\n const onClick = () => c;\n const onSubmit = () => c;\n const defaultValue = d + 1;\n bar();\n}', + errors: [{ messageId: 'globalUnsorted' }], + output: + 'function f() {\n const { b, c } = useBC();\n\n const { d, e } = useDE(\n b\n );\n\n const defaultValue = d + 1;\n const onClick = () => c;\n const onSubmit = () => c;\n\n bar();\n}' + }, + // multilineNewline — var declarations + { + code: 'function f() {\n const a = useMemo(\n () => 1,\n []\n );\n const b = useMemo(\n () => 2,\n []\n );\n}', + errors: [{ messageId: 'multilineNewline' }], + output: + 'function f() {\n const a = useMemo(\n () => 1,\n []\n );\n\n const b = useMemo(\n () => 2,\n []\n );\n}' + }, + { + code: 'function f() {\n const x = 1;\n const form = useForm({\n resolver: r\n });\n}', + errors: [{ messageId: 'multilineNewline' }], + output: 'function f() {\n const x = 1;\n\n const form = useForm({\n resolver: r\n });\n}' + }, + // multilineNewline — non-var statements + { + code: 'function f() {\n useEffect(() => {\n doSomething();\n }, []);\n useEffect(() => {\n doOther();\n }, []);\n}', + errors: [{ messageId: 'multilineNewline' }], + output: + 'function f() {\n useEffect(() => {\n doSomething();\n }, []);\n\n useEffect(() => {\n doOther();\n }, []);\n}' + }, + // unnecessaryNewline extended for block — same group, one-liner, no dep + // regression: dep on earlier member of group (not just prevDeclaration) should not trigger unnecessaryNewline + { + code: 'function f() {\n const { getAsset } = useAssets();\n const { t } = useTranslation();\n const asset = getAsset(balance);\n}', + errors: [{ data: { dependency: 'getAsset' }, messageId: 'missingNewline' }], + output: + 'function f() {\n const { getAsset } = useAssets();\n const { t } = useTranslation();\n\n const asset = getAsset(balance);\n}' + }, + { + code: 'function f() {\n const a = useForm();\n\n const b = 1;\n}', + errors: [{ messageId: 'unnecessaryNewline' }], + output: 'function f() {\n const a = useForm();\n const b = 1;\n}' + }, + { + code: 'function f() {\n const a = useMemo(() => 1, []);\n\n const b = useMemo(() => 2, []);\n}', + errors: [{ messageId: 'unnecessaryNewline' }], + output: 'function f() {\n const a = useMemo(() => 1, []);\n const b = useMemo(() => 2, []);\n}' + }, + // unnecessaryNewline — blank line between siblings that both depend on an earlier group var (not on each other) + { + code: 'function f() {\n const a = useA();\n\n const b = useB(a);\n\n const c = useC(a);\n}', + errors: [{ messageId: 'unnecessaryNewline' }], + output: 'function f() {\n const a = useA();\n\n const b = useB(a);\n const c = useC(a);\n}' + }, + // unnecessaryNewline for consecutive single-line non-vars with blank + { + code: 'function f() {\n useEffect(() => {}, []);\n\n useEffect(() => {}, []);\n}', + errors: [{ messageId: 'unnecessaryNewline' }], + output: 'function f() {\n useEffect(() => {}, []);\n useEffect(() => {}, []);\n}' + }, + // guardClauseNewline — inline guard clause must be followed by a blank line + { + code: 'function f() {\n if (x) return;\n doSomething();\n}', + errors: [{ messageId: 'guardClauseNewline' }], + output: 'function f() {\n if (x) return;\n\n doSomething();\n}' + }, + { + code: 'const onScroll = event => {\n if (ref.current?.contains(event.target)) return;\n onClickOutside();\n};', + errors: [{ messageId: 'guardClauseNewline' }], + output: + 'const onScroll = event => {\n if (ref.current?.contains(event.target)) return;\n\n onClickOutside();\n};' + }, + // console.log is a hard boundary — vars on each side are sorted within their segment but never across + { + code: 'function f() {\n const z = 1;\n const m = 2;\n console.log(m);\n const b = 3;\n const a = 4;\n}', + errors: [{ messageId: 'globalUnsorted' }, { messageId: 'globalUnsorted' }], + output: 'function f() {\n const m = 2;\n const z = 1;\n console.log(m);\n const a = 4;\n const b = 3;\n}' + }, + // console.log preserved when only the trailing var segment is unsorted + { + code: 'function f() {\n const z = 1;\n console.log(z);\n const m = 2;\n const a = 3;\n}', + errors: [{ messageId: 'globalUnsorted' }], + output: 'function f() {\n const z = 1;\n console.log(z);\n const a = 3;\n const m = 2;\n}' + }, + // any console method (console.error, console.warn, etc.) acts as a boundary + { + code: 'function f() {\n console.error("err");\n const z = 1;\n const a = 2;\n}', + errors: [{ messageId: 'globalUnsorted' }], + output: 'function f() {\n console.error("err");\n const a = 2;\n const z = 1;\n}' + }, + // project convention: get* computed key — computed member where key is a convention getter is sortable + { + code: 'const z = obj[getKey()];\nconst a = obj[getKey()];', + errors: [{ messageId: 'unsorted' }], + output: 'const a = obj[getKey()];\nconst z = obj[getKey()];' + }, + // allowlist: two literal initializers — should sort + { + code: 'const z = 1;\nconst a = 2;', + errors: [{ messageId: 'unsorted' }], + output: 'const a = 2;\nconst z = 1;' + }, + // category 7 — plain member access is sortable (known limitation: getter side effects are statically undetectable) + // note: treating all member access as impure would block thousands of legitimate sorts + { + code: 'const z = obj.zoo;\nconst a = obj.alpha;', + errors: [{ messageId: 'unsorted' }], + output: 'const a = obj.alpha;\nconst z = obj.zoo;' + }, + // pure-init let declarations are sortable (known limitation: downstream reassignment is invisible to the rule) + // note: reordering pure-init lets does not change observable behavior since the initializers have no side effects + { + code: 'let z = 1;\nlet a = 2;', + errors: [{ messageId: 'unsorted' }], + output: 'let a = 2;\nlet z = 1;' + }, + // spreading an identifier is sortable (known limitation: rule cannot detect that sharedIter is a stateful iterator) + // note: the rule treats Identifier as pure; spreading the same identifier twice is an undetectable side-effect pattern + { + code: 'const z = [...sharedIter];\nconst a = [...sharedIter];', + errors: [{ messageId: 'unsorted' }], + output: 'const a = [...sharedIter];\nconst z = [...sharedIter];' + }, + // allowlist: two arrow function initializers — should sort + { + code: 'const z = () => doStuff();\nconst a = () => doOther();', + errors: [{ messageId: 'unsorted' }], + output: 'const a = () => doOther();\nconst z = () => doStuff();' + }, + // allowlist: two member-access initializers — should sort + { + code: 'const z = props.foo;\nconst a = props.bar;', + errors: [{ messageId: 'unsorted' }], + output: 'const a = props.bar;\nconst z = props.foo;' + }, + // allowlist: hook calls — should sort + { + code: 'const z = useFoo();\nconst a = useBar();', + errors: [{ messageId: 'unsorted' }], + output: 'const a = useBar();\nconst z = useFoo();' + }, + // allowlist: namespaced hook calls (React.useMemo, React.useCallback) — should sort + { + code: 'const z = React.useMemo(() => 1, []);\nconst a = React.useMemo(() => 2, []);', + errors: [{ messageId: 'unsorted' }], + output: 'const a = React.useMemo(() => 2, []);\nconst z = React.useMemo(() => 1, []);' + }, + // allowlist: JSX initializers — should sort + { + code: 'const z = ;\nconst a = ;', + errors: [{ messageId: 'unsorted' }], + output: 'const a = ;\nconst z = ;', + parserOptions: { ecmaFeatures: { jsx: true }, ecmaVersion: 2020 } + }, + // allowlist: useMemo with non-hook calls inside the deferred body — should sort + { + code: 'const z = useMemo(() => doStuff(), []);\nconst a = useMemo(() => doOther(), []);', + errors: [{ messageId: 'unsorted' }], + output: 'const a = useMemo(() => doOther(), []);\nconst z = useMemo(() => doStuff(), []);' + }, + // allowlist (built-ins): global fn casts + { + code: 'const z = Number(x);\nconst a = Number(y);', + errors: [{ messageId: 'unsorted' }], + output: 'const a = Number(y);\nconst z = Number(x);' + }, + // allowlist (built-ins): global parseInt + { + code: "const z = parseInt('5', 10);\nconst a = parseInt('3', 10);", + errors: [{ messageId: 'unsorted' }], + output: "const a = parseInt('3', 10);\nconst z = parseInt('5', 10);" + }, + // allowlist (built-ins): global isNaN + { + code: 'const z = isNaN(x);\nconst a = isNaN(y);', + errors: [{ messageId: 'unsorted' }], + output: 'const a = isNaN(y);\nconst z = isNaN(x);' + }, + // allowlist (built-ins): Object.freeze + { + code: 'const z = Object.freeze({});\nconst a = Object.freeze({});', + errors: [{ messageId: 'unsorted' }], + output: 'const a = Object.freeze({});\nconst z = Object.freeze({});' + }, + // allowlist (built-ins): Object.keys + { + code: 'const z = Object.keys(x);\nconst a = Object.keys(y);', + errors: [{ messageId: 'unsorted' }], + output: 'const a = Object.keys(y);\nconst z = Object.keys(x);' + }, + // allowlist (built-ins): JSON.stringify + { + code: 'const z = JSON.stringify(x);\nconst a = JSON.stringify(y);', + errors: [{ messageId: 'unsorted' }], + output: 'const a = JSON.stringify(y);\nconst z = JSON.stringify(x);' + }, + // allowlist (built-ins): Math.max / Math.min (deterministic) + { + code: 'const z = Math.max(1, 2);\nconst a = Math.min(3, 4);', + errors: [{ messageId: 'unsorted' }], + output: 'const a = Math.min(3, 4);\nconst z = Math.max(1, 2);' + }, + // allowlist (built-ins): Array.from + { + code: 'const z = Array.from(x);\nconst a = Array.from(y);', + errors: [{ messageId: 'unsorted' }], + output: 'const a = Array.from(y);\nconst z = Array.from(x);' + }, + // allowlist (built-ins): PropTypes.oneOf + { + code: "const z = PropTypes.oneOf(['z']);\nconst a = PropTypes.oneOf(['a']);", + errors: [{ messageId: 'unsorted' }], + output: "const a = PropTypes.oneOf(['a']);\nconst z = PropTypes.oneOf(['z']);" + }, + // allowlist (built-ins): String prototype .toLowerCase() + { + code: 'const z = name.toLowerCase();\nconst a = type.toLowerCase();', + errors: [{ messageId: 'unsorted' }], + output: 'const a = type.toLowerCase();\nconst z = name.toLowerCase();' + }, + // allowlist (built-ins): String prototype .split() + { + code: "const z = path.split('.');\nconst a = name.split('.');", + errors: [{ messageId: 'unsorted' }], + output: "const a = name.split('.');\nconst z = path.split('.');" + }, + // allowlist (built-ins): Array/String .slice() + { + code: 'const z = arr.slice(0, 2);\nconst a = list.slice(0, 2);', + errors: [{ messageId: 'unsorted' }], + output: 'const a = list.slice(0, 2);\nconst z = arr.slice(0, 2);' + }, + // allowlist (built-ins): .toString() on identifier + { + code: 'const z = isOpen.toString();\nconst a = isReady.toString();', + errors: [{ messageId: 'unsorted' }], + output: 'const a = isReady.toString();\nconst z = isOpen.toString();' + }, + // allowlist (built-ins): .includes() + { + code: "const z = path.includes('foo');\nconst a = name.includes('bar');", + errors: [{ messageId: 'unsorted' }], + output: "const a = name.includes('bar');\nconst z = path.includes('foo');" + }, + // project convention: get* Identifier + { + code: 'const z = getRow();\nconst a = getLabel();', + errors: [{ messageId: 'unsorted' }], + output: 'const a = getLabel();\nconst z = getRow();' + }, + // project convention: is* Identifier + { + code: 'const z = isEmpty(x);\nconst a = isCrypto(x);', + errors: [{ messageId: 'unsorted' }], + output: 'const a = isCrypto(x);\nconst z = isEmpty(x);' + }, + // project convention: has* Identifier + { + code: 'const z = hasFoo();\nconst a = hasBar();', + errors: [{ messageId: 'unsorted' }], + output: 'const a = hasBar();\nconst z = hasFoo();' + }, + // project convention: format* Identifier + { + code: 'const z = formatAmount(1);\nconst a = formatTime(2);', + errors: [{ messageId: 'unsorted' }], + output: 'const a = formatTime(2);\nconst z = formatAmount(1);' + }, + // project convention: t Identifier (i18n) + { + code: "const z = t('a');\nconst a = t('b');", + errors: [{ messageId: 'unsorted' }], + output: "const a = t('b');\nconst z = t('a');" + }, + // project convention: PascalCase namespace + get* + { + code: 'const z = DateHelper.getFormattedDate(d);\nconst a = DateHelper.getFormattedTime(d);', + errors: [{ messageId: 'unsorted' }], + output: 'const a = DateHelper.getFormattedTime(d);\nconst z = DateHelper.getFormattedDate(d);' + }, + // project convention: PascalCase namespace + t + { + code: "const z = I18n.t('z');\nconst a = I18n.t('a');", + errors: [{ messageId: 'unsorted' }], + output: "const a = I18n.t('a');\nconst z = I18n.t('z');" + }, + // project convention: PascalCase namespace + is* + { + code: 'const z = AssetHelper.isCrypto(x);\nconst a = AssetHelper.isFiat(x);', + errors: [{ messageId: 'unsorted' }], + output: 'const a = AssetHelper.isFiat(x);\nconst z = AssetHelper.isCrypto(x);' + } + ], + valid: [ + { code: 'const a = 1;' }, + // uppercase before lowercase is already correct — no error + { code: 'const Garden = {};\nconst ball1 = {};' }, + { code: '' }, + { code: 'const a = 1;\nconst b = 2;\nconst c = 3;' }, + { code: 'const [alpha] = [];\nconst { beta } = {};\nconst gamma = 3;' }, + { code: 'const [alpha] = [];\nconst [beta] = [];' }, + { code: 'const { alpha } = {};\nconst { beta } = {};' }, + { code: 'const a = require("a");\nconst b = require("b");' }, + { code: 'const z = require("z");\nconst a = require("a");' }, + { code: 'const z = 1;\n\nconst a = 2;' }, + { code: 'const a = 1;\nconst z = 2;\n\nconst m = 3;\nconst n = 4;' }, + { code: 'const a = 1;\nfoo();\nconst a2 = 2;\nconst z = 3;' }, + { code: 'function foo() {\n const a = 1;\n const b = 2;\n}' }, + { code: 'const a = 1;\n\nconst b = a + 1;' }, + { code: 'const a = 1;\nconst b = 2;\n\nconst c = a + b;' }, + { code: 'const a = 1;\nfoo();\nconst b = a + 1;' }, + { code: 'const a = {\n x: 1\n};\n\nconst b = {\n y: 2\n};' }, + { code: 'const a = getValue();\nconst b = getValue2();' }, + // regression: sibling decls sharing a used name but differing in module-level group (CONST vs CONST_CALL) + // must keep the blank line between them — removing it would loop with sort-module-declarations + { + code: 'const summaryFile = process.env.GITHUB_STEP_SUMMARY;\n\nconst [command, ...args] = process.argv.slice(2);' + }, + { code: 'const a = obj.foo;\n\nconst b = obj.bar();' }, + { code: 'const a = new Obj();\n\nconst b = obj.prop;' }, + { code: 'function foo() {\n const a = 1;\n\n const b = a + 1;\n}' }, + { code: 'function f() {\n const a = 1;\n const b = 2;\n\n useEffect(() => {}, []);\n}' }, + { + code: 'function f() {\n const a = 1;\n const h = useForm();\n\n const m = useMemo(() => a, [a]);\n\n const c = useCallback(() => {}, []);\n\n useEffect(() => {}, []);\n}' + }, + { code: 'function f() {\n const a = 1;\n\n useEffect(() => {}, []);\n}' }, + { + code: 'function f() {\n const a = useMemo(\n () => 1,\n []\n );\n\n const b = useMemo(\n () => 2,\n []\n );\n}' + }, + { code: 'function f() {\n const a = useMemo(() => 1, []);\n const b = useMemo(() => 2, []);\n}' }, + { code: 'function f() {\n useEffect(() => {}, []);\n useEffect(() => {}, []);\n}' }, + { + code: 'function f() {\n useEffect(() => {\n doSomething();\n }, []);\n\n useEffect(() => {\n doOther();\n }, []);\n}' + }, + { code: 'function f() {\n const a = useMemo(() => 1, []);\n\n const b = useCallback(() => {}, []);\n}' }, + // regression: blank line before a var that uses an earlier (non-immediate) member of the same group must be valid + { + code: 'function f() {\n const { getAsset } = useAssets();\n const { t } = useTranslation();\n\n const asset = getAsset(balance);\n}' + }, + // comment separated by blank line from its declaration — valid when already sorted + { code: 'function f() {\n // comment\n\n const a = 2;\n const z = 1;\n}' }, + // blank line before return statement is idiomatic and should always be allowed + { code: 'function f() {\n doSomething();\n\n return x;\n}' }, + { code: 'function f() {\n set(obj, key, val);\n\n return this;\n}' }, + // inline guard clause with blank line after — valid + { code: 'function f() {\n if (x) return;\n\n doSomething();\n}' }, + { + code: 'const onScroll = event => {\n if (ref.current?.contains(event.target)) return;\n\n onClickOutside();\n};' + }, + // block-form guard clause (with or without blank line) — ok as is + { code: 'function f() {\n if (x) {\n return;\n }\n\n doSomething();\n}' }, + { code: 'function f() {\n if (x) { return; }\n doSomething();\n}' }, + // chained inline guard clauses — no blank line needed between them + { code: 'const step = () => {\n if (a) return STEPS.A;\n if (b) return STEPS.B;\n if (c) return STEPS.C;\n};' }, + // inline guard clause followed by another if — no blank line required + { code: 'function f() {\n if (x) return;\n if (y) doSomething();\n}' }, + // if statement is a hard boundary — vars on either side must not be reordered across it + { code: 'function f() {\n const z = 1;\n if (z) {}\n const a = 2;\n}' }, + { code: 'function f() {\n const z = 1;\n if (!z) { return; }\n const a = 2;\n const b = 3;\n}' }, + // regression: inner const declarations inside a callback should not create false cross-declaration dependencies + { + code: 'function f() {\n const { dispatch, useMachineContext } = useMachineInterpreter(machine, {\n actions: {\n update: ({ validTargets }) => {\n const [lockedNode] = validTargets;\n return lockedNode;\n }\n }\n });\n\n const [{ validTargets }] = useMachineContext([]);\n\n const [lockedNode] = validTargets;\n}' + }, + // regression: mutual body references create a cycle — original order must be preserved (no-use-before-define safe) + { + code: 'function f() {\n const handleErrors = useCallback((err) => { onReset(err); }, []);\n\n const onReset = useCallback(async (token) => { await handleErrors(token); }, [handleErrors]);\n}' + }, + // regression: inner const inside try block should not falsely depend on outer var with same name — topo order preserved + { + code: 'function f() {\n const [item, setItem] = useState(null);\n\n const onFetch = useCallback(async () => {\n try {\n const item = await fetchItem();\n setItem(item);\n } catch (e) {}\n }, []);\n}' + }, + // worklet directive must always remain as the first statement in a function body + { code: "const f = () => {\n 'worklet';\n\n const a = 1;\n const b = 2;\n};" }, + { code: "const f = () => {\n 'worklet';\n\n const a = 1;\n\n return a;\n};" }, + // super() is a hard boundary — vars after super() must not be hoisted before it + { + code: 'class Foo extends Bar {\n constructor() {\n const credentials = getCredentials();\n super(credentials);\n const request = this.client.request.bind(this.client);\n this.client.request = () => {};\n this.storage = getStorage();\n }\n}', + parserOptions: { ecmaVersion: 2020 } + }, + // super() inside try block is still a hard boundary — vars after the try must not be hoisted before it + { + code: 'class Foo extends Bar {\n constructor(opts) {\n const x = 1;\n try {\n super(opts);\n } catch (e) {}\n const y = this.something;\n }\n}', + parserOptions: { ecmaVersion: 2020 } + }, + // vars using `this` must not be hoisted past non-var `this.*` calls they may depend on + { + code: 'class Foo extends Bar {\n constructor() {\n super();\n this.setup();\n const x = this.getValue();\n this.configure();\n const y = this.getOther();\n }\n}', + parserOptions: { ecmaVersion: 2020 } + }, + // one-at-a-time topo sort places one-liners in alpha order — defaultValue before onClick/onSubmit + { + code: 'function f() {\n const { b, c } = useBC();\n\n const { d, e } = useDE(\n b\n );\n\n const defaultValue = d + 1;\n const onClick = () => c;\n const onSubmit = () => c;\n\n bar();\n}' + }, + // independent identifier (no deps) stays before same-group ObjectPattern whose dep just became available + { + code: 'function f() {\n const { a } = useA();\n const x = useX();\n\n const { b } = useB(a);\n\n foo();\n}' + }, + // stable: one-liner no-deps decl precedes multi-liner no-deps decl, and transitionToDep blank is preserved (no autofix oscillation) + { + code: "function f() {\n const { t } = useTranslation();\n\n const {\n control,\n watch\n } = useFormContext();\n\n const [amount] = watch(['amount']);\n const msg = t('x');\n\n foo();\n}" + }, + // non-hook call initializers after non-var statements must not be reordered (side-effect dependency) + { code: 'function f() {\n setup();\n const result = computeResult();\n}' }, + { + code: 'function f() {\n render(component);\n const el = screen.getByRole("tab");\n const other = screen.getByRole("button");\n}' + }, + // console.log between two single vars — each segment has nothing to sort, console.log stays in place + { code: 'function f() {\n const z = 1;\n console.log(z);\n const a = 2;\n}' }, + // multiple console statements — each acts as a boundary, vars on each side already in order + { + code: 'function f() {\n const a = 1;\n console.log(a);\n const b = 2;\n console.error(b);\n const c = 3;\n}' + }, + // console.log at module level — vars on each side already sorted, no cross-segment sort + { code: 'const a = 1;\nconsole.log(a);\nconst b = 2;' }, + // regression: a non-var statement positioned between two vars where the second var awaits a value + // produced by the first must not be reordered — moving the await up would skip the synchronous + // side effect required to resolve the awaited promise (e.g. `callback()` triggering `resolveStep`) + { + code: + 'async function emitEvent(callback) {\n' + + ' const stepPromise = makePromise();\n' + + '\n' + + ' callback();\n' + + '\n' + + ' const result = await stepPromise;\n' + + '\n' + + ' doCleanup();\n' + + '}' + }, + // regression: `yield` in a generator var init is also an ordering boundary — non-vars around it + // must not be reordered + { + code: + 'function* gen() {\n' + + ' const queue = makeQueue();\n' + + '\n' + + ' enqueue(queue);\n' + + '\n' + + ' const next = yield queue;\n' + + '\n' + + ' finalize(next);\n' + + '}' + }, + // regression: even when all vars precede non-vars, vars must not be reordered alphabetically when + // doing so would move a side-effecting initializer across an await — e.g. setTimeout that arms a + // timer must run before the await it is meant to abort, not after + { + code: + 'async function fetchWithTimeout(input, options) {\n' + + ' const { timeout } = options;\n' + + ' const controller = new AbortController();\n' + + '\n' + + ' const timeoutId = setTimeout(() => controller.abort(), timeout);\n' + + ' const response = await fetch(input, { signal: controller.signal });\n' + + '\n' + + ' clearTimeout(timeoutId);\n' + + '\n' + + ' return response;\n' + + '}' + }, + // mutation (pop): side-effecting call must not be reordered + { code: 'const z = stack.pop();\nconst a = stack.pop();' }, + // mutation (push): side-effecting call must not be reordered + { code: 'const z = arr.push(1);\nconst a = arr.push(2);' }, + // mutation (set add): side-effecting call must not be reordered + { code: "const z = mySet.add('z');\nconst a = mySet.add('a');" }, + // counter / ID generation: each call returns the next ID in sequence + { code: 'const zId = nextId();\nconst aId = nextId();' }, + // logging / IO: each call produces an observable side effect + { code: "const z = logger.info('z');\nconst a = logger.info('a');" }, + // iterator advance: each next() consumes the next element + { code: 'const z = iter.next().value;\nconst a = iter.next().value;' }, + // computed member access where key is a convention getter (pure): sortable + // note: getKey() matches the get* convention — reordering is safe + // clock (Date.now): each call captures a different instant + { code: 'const zNow = Date.now();\nconst aNow = Date.now();' }, + // clock (performance.now): each call captures a different instant + { code: 'const zT = performance.now();\nconst aT = performance.now();' }, + // randomness (Math.random): each call returns a different value + { code: 'const zRand = Math.random();\nconst aRand = Math.random();' }, + // cryptographic randomness: each call returns different bytes + { code: 'const zBytes = crypto.randomBytes(16);\nconst aBytes = crypto.randomBytes(16);' }, + // UUID generation: each call returns a unique identifier + { code: 'const zId = uuid();\nconst aId = uuid();' }, + // new Date() constructor: each instantiation captures the current time + { code: 'const zDate = new Date();\nconst aDate = new Date();' }, + // fetch ordering: promise creation order matters for concurrent effects + { code: 'const z = fetchFoo();\nconst a = fetchBar();' }, + // side-effecting promise factory: each call starts a job in order + { code: "const zPromise = startJob('z');\nconst aPromise = startJob('a');" }, + // throw ordering: each call may throw; order affects which error surfaces first + { code: 'const z = parseStrict(input);\nconst a = audit(input);' }, + // SQL tagged template: tag function may have side effects (query registration, etc.) + { code: 'const z = sql`SELECT z`;\nconst a = sql`SELECT a`;' }, + // GraphQL tagged template: gql tag may register or deduplicate queries + { code: 'const z = gql`query Z { z }`;\nconst a = gql`query A { a }`;' }, + // HTML/styled tagged template: tag function may have side effects + { code: 'const z = html`
z
`;\nconst a = html`
a
`;' }, + // impure init + downstream reassignment: reordering would change observable behavior + { code: 'let z = nextId();\nlet a = nextId();\nz = a;' }, + // spread of call result (stateful iterable) must not be reordered + { code: 'const z = [...gen()];\nconst a = [...gen()];' }, + // new with side effects (no args): constructor may register or mutate global state + { code: 'const z = new EventEmitter();\nconst a = new AbortController();' }, + // new with arguments: constructor arguments do not make the expression pure + { code: 'const z = new MyClass(1, 2);\nconst a = new MyClass(3, 4);' }, + // allowlist (built-ins): Math.random is non-deterministic — must not sort + { code: 'const z = Math.random();\nconst a = Math.random();' }, + // allowlist (built-ins): Symbol() constructor returns a unique value each call — must not sort + { code: "const z = Symbol('z');\nconst a = Symbol('a');" }, + // allowlist (built-ins): Date.now is non-deterministic — must not sort + { code: 'const z = Date.now();\nconst a = Date.now();' }, + // allowlist (built-ins): history.replace has navigation side effects — must not sort + { code: "const z = history.replace('/z');\nconst a = history.replace('/a');" }, + // allowlist (built-ins): regex.exec mutates lastIndex on global regexes — must not sort + { code: 'const z = pattern.exec(strA);\nconst a = pattern.exec(strB);' }, + // allowlist (built-ins): .replace on arbitrary identifier (could be history.replace) — must not sort + { code: "const z = router.replace('/z');\nconst a = router.replace('/a');" }, + // project convention: lowercase namespace + getter — must not sort (namespace not PascalCase) + { code: "const z = localStorage.getItem('z');\nconst a = localStorage.getItem('a');" }, + // project convention: lowercase namespace + .get — must not sort + { code: "const z = obj.get('z');\nconst a = obj.get('a');" }, + // project convention: camelCase namespace — must not sort (not PascalCase) + { code: 'const z = props.getRow();\nconst a = props.getLabel();' }, + // project convention: wrong prefix (do*) — must not sort + { code: 'const z = doSomething();\nconst a = doOther();' }, + // project convention: mutation verbs — must not sort + { code: 'const z = createCard(x);\nconst a = createUser(y);' }, + // project convention: mutation verbs as PascalCase members — must not sort + { code: 'const z = CardService.createCard(x);\nconst a = UserService.deleteUser(y);' }, + // project convention: bare prefix (no suffix) — must not sort + { code: 'const z = get();\nconst a = is();' }, + // project convention: lowercase next char (getter) — must not sort + { code: 'const z = getter();\nconst a = setter();' }, + // project convention: lowercase next char (formatter) — must not sort + { code: 'const z = formatter(x);\nconst a = formatter(y);' }, + // project convention: lowercase next char (gettext) — must not sort + { code: "const z = gettext('z');\nconst a = gettext('a');" }, + // cross-statement guard: isDateInLastMonth (is* convention) after a side-effecting non-var must not be reordered + { + code: 'function f() {\n const date = "2024-01-01";\n MockDate.set(now);\n const result = isDateInLastMonth(date);\n}' + }, + // cross-statement guard: convention-matching call (is*) must not cross side-effecting non-var statement + { code: 'function f() {\n const a = 1;\n setupMock();\n const z = isReady(state);\n}' }, + // non-convention call as computed key — order matters (e.g., counter) + { code: 'const z = obj[nextKey()];\nconst a = obj[nextKey()];' }, + // allowlist (built-ins): PropTypes.checkPropTypes is side-effecting (logs warnings) — must not sort + { code: 'const z = PropTypes.checkPropTypes(s, p, l, n);\nconst a = PropTypes.checkPropTypes(s2, p2, l2, n2);' }, + // allowlist (built-ins): PropTypes.resetWarningCache mutates internal state — must not sort + { code: 'const z = PropTypes.resetWarningCache();\nconst a = PropTypes.resetWarningCache();' } + ] +}); + +const tsRuleTester = new RuleTester({ + parser: require.resolve('@typescript-eslint/parser'), + parserOptions: { ecmaFeatures: { jsx: true }, ecmaVersion: 2020 } +}); + +tsRuleTester.run('sort-variable-declarations (typescript)', rule, { + invalid: [ + // regression: `typeof onSelect` in a parameter type annotation is a real dependency — renderItem must come after onSelect + { + code: 'function f() {\n const { dispatch } = useMachine();\n\n const renderItem = useCallback(\n ({ x }: { x: typeof onSelect }) => x,\n []\n );\n\n const onSelect = useCallback(() => dispatch(), [dispatch]);\n}', + errors: [{ messageId: 'globalUnsorted' }], + output: + 'function f() {\n const { dispatch } = useMachine();\n\n const onSelect = useCallback(() => dispatch(), [dispatch]);\n\n const renderItem = useCallback(\n ({ x }: { x: typeof onSelect }) => x,\n []\n );\n}' + }, + // regression: `typeof y` in a declarator id type annotation is a real dependency — x must come after y + { + code: 'function f() {\n const x: typeof y = null;\n const y = 1;\n\n foo();\n}', + errors: [{ messageId: 'globalUnsorted' }], + output: 'function f() {\n const y = 1;\n\n const x: typeof y = null;\n\n foo();\n}' + }, + // purely type-only references (not `typeof`) must NOT create value-level deps — + // uppercase `Transaction` must sort above lowercase `handler` + { + code: 'function f() {\n const handler = (_x: Transaction) => 1;\n const Transaction = 2;\n}', + errors: [{ messageId: 'globalUnsorted' }], + output: 'function f() {\n const Transaction = 2;\n const handler = (_x: Transaction) => 1;\n}' + } + ], + valid: [ + // regression: when the later multi-liner uses `typeof onSelect` in its param type, + // the dep must keep onSelect above it — never pick the no-deps multi-liner first + 'function f() {\n const { dispatch } = useMachine();\n\n const onSelect = useCallback(() => dispatch(), [dispatch]);\n\n const renderItem = useCallback(\n ({ x }: { x: typeof onSelect }) => x,\n []\n );\n}', + // typeof in a return type annotation is also a dependency + 'function f() {\n const base = 1;\n\n const compute = (): typeof base => base;\n}' + ] +});