WIP: bootstrap and partial real Solana watcher implementation

This commit is contained in:
2026-08-16 09:17:45 +00:00
commit dc23412c3f
7232 changed files with 1687637 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
function _temporalUndefined() {}
export { _temporalUndefined as default };

View File

@@ -0,0 +1 @@
{"version":3,"file":"characterCodes.enum.d.ts","sourceRoot":"","sources":["../../src/enums/characterCodes.enum.ts"],"names":[],"mappings":"AAAA,oBAAY,cAAc;IACtB,GAAG,KAAK;IACR,aAAa,IAAI;IACjB,iBAAiB,MAAO;IAExB,QAAQ,KAAO,CAAE,KAAK;IACtB,cAAc,KAAO,CAAE,KAAK;IAC5B,aAAa,OAAS;IACtB,kBAAkB,OAAS;IAC3B,QAAQ,MAAS;IAGjB,KAAK,KAAS,CAAE,MAAM;IACtB,gBAAgB,MAAS,CAAE,EAAE;IAC7B,MAAM,OAAS;IACf,MAAM,OAAS;IACf,OAAO,OAAS;IAChB,OAAO,OAAS;IAChB,eAAe,OAAS;IACxB,cAAc,OAAS;IACvB,aAAa,OAAS;IACtB,WAAW,OAAS;IACpB,gBAAgB,OAAS;IACzB,SAAS,OAAS;IAClB,SAAS,OAAS;IAClB,cAAc,OAAS;IACvB,kBAAkB,OAAS;IAC3B,gBAAgB,QAAS;IACzB,iBAAiB,OAAS;IAC1B,KAAK,OAAS;IAGd,oBAAoB,QAAS;IAE7B,CAAC,KAAO;IACR,CAAC,KAAO;IAER,EAAE,KAAO;IACT,EAAE,KAAO;IACT,EAAE,KAAO;IACT,EAAE,KAAO;IACT,EAAE,KAAO;IACT,EAAE,KAAO;IACT,EAAE,KAAO;IACT,EAAE,KAAO;IACT,EAAE,KAAO;IACT,EAAE,KAAO;IAET,CAAC,KAAO;IACR,CAAC,KAAO;IACR,CAAC,KAAO;IACR,CAAC,MAAO;IACR,CAAC,MAAO;IACR,CAAC,MAAO;IACR,CAAC,MAAO;IACR,CAAC,MAAO;IACR,CAAC,MAAO;IACR,CAAC,MAAO;IACR,CAAC,MAAO;IACR,CAAC,MAAO;IACR,CAAC,MAAO;IACR,CAAC,MAAO;IACR,CAAC,MAAO;IACR,CAAC,MAAO;IACR,CAAC,MAAO;IACR,CAAC,MAAO;IACR,CAAC,MAAO;IACR,CAAC,MAAO;IACR,CAAC,MAAO;IACR,CAAC,MAAO;IACR,CAAC,MAAO;IACR,CAAC,MAAO;IACR,CAAC,MAAO;IACR,CAAC,MAAO;IAER,CAAC,KAAO;IACR,CAAC,KAAO;IACR,CAAC,KAAO;IACR,CAAC,KAAO;IACR,CAAC,KAAO;IACR,CAAC,KAAO;IACR,CAAC,KAAO;IACR,CAAC,KAAO;IACR,CAAC,KAAO;IACR,CAAC,KAAO;IACR,CAAC,KAAO;IACR,CAAC,KAAO;IACR,CAAC,KAAO;IACR,CAAC,KAAO;IACR,CAAC,KAAO;IACR,CAAC,KAAO;IACR,CAAC,KAAO;IACR,CAAC,KAAO;IACR,CAAC,KAAO;IACR,CAAC,KAAO;IACR,CAAC,KAAO;IACR,CAAC,KAAO;IACR,CAAC,KAAO;IACR,CAAC,KAAO;IACR,CAAC,KAAO;IACR,CAAC,KAAO;IAER,SAAS,KAAO,CAAE,IAAI;IACtB,QAAQ,KAAO,CAAE,IAAI;IACrB,EAAE,KAAO,CAAE,IAAI;IACf,SAAS,KAAO,CAAE,IAAI;IACtB,QAAQ,KAAO,CAAE,IAAI;IACrB,GAAG,MAAO,CAAE,IAAI;IAChB,KAAK,KAAO,CAAE,IAAI;IAClB,UAAU,MAAO,CAAE,IAAI;IACvB,YAAY,KAAO,CAAE,IAAI;IACzB,UAAU,KAAO,CAAE,IAAI;IACvB,KAAK,KAAO,CAAE,IAAI;IAClB,KAAK,KAAO,CAAE,IAAI;IAClB,GAAG,KAAO,CAAE,IAAI;IAChB,WAAW,KAAO,CAAE,IAAI;IACxB,MAAM,KAAO,CAAE,IAAI;IACnB,WAAW,KAAO,CAAE,IAAI;IACxB,WAAW,KAAO,CAAE,IAAI;IACxB,IAAI,KAAO,CAAE,IAAI;IACjB,QAAQ,KAAO,CAAE,IAAI;IACrB,KAAK,KAAO,CAAE,IAAI;IAClB,SAAS,MAAO,CAAE,IAAI;IACtB,WAAW,KAAO,CAAE,IAAI;IACxB,SAAS,KAAO,CAAE,IAAI;IACtB,OAAO,KAAO,CAAE,IAAI;IACpB,IAAI,KAAO,CAAE,IAAI;IACjB,QAAQ,KAAO,CAAE,IAAI;IACrB,SAAS,KAAO,CAAE,IAAI;IACtB,WAAW,KAAO,CAAE,IAAI;IACxB,KAAK,KAAO,CAAE,IAAI;IAClB,KAAK,MAAO,CAAE,IAAI;IAElB,SAAS,IAAO,CAAE,KAAK;IACvB,QAAQ,KAAO,CAAE,KAAK;IACtB,aAAa,QAAS;IACtB,GAAG,IAAO,CAAE,KAAK;IACjB,WAAW,KAAO;CACrB"}

View File

@@ -0,0 +1,19 @@
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABILITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
interface ArrayBufferConstructor {
new (): ArrayBuffer;
}

View File

@@ -0,0 +1,75 @@
# pg-types
This is the code that turns all the raw text from postgres into JavaScript types for [node-postgres](https://github.com/brianc/node-postgres.git)
## use
This module is consumed and exported from the root `pg` object of node-postgres. To access it, do the following:
```js
var types = require('pg').types
```
Generally what you'll want to do is override how a specific data-type is parsed and turned into a JavaScript type. By default the PostgreSQL backend server returns everything as strings. Every data type corresponds to a unique `OID` within the server, and these `OIDs` are sent back with the query response. So, you need to match a particluar `OID` to a function you'd like to use to take the raw text input and produce a valid JavaScript object as a result. `null` values are never parsed.
Let's do something I commonly like to do on projects: return 64-bit integers `(int8)` as JavaScript integers. Because JavaScript doesn't have support for 64-bit integers node-postgres cannot confidently parse `int8` data type results as numbers because if you have a _huge_ number it will overflow and the result you'd get back from node-postgres would not be the result in the datbase. That would be a __very bad thing__ so node-postgres just returns `int8` results as strings and leaves the parsing up to you. Let's say that you know you don't and wont ever have numbers greater than `int4` in your database, but you're tired of recieving results from the `COUNT(*)` function as strings (because that function returns `int8`). You would do this:
```js
var types = require('pg').types
types.setTypeParser(20, function(val) {
return parseInt(val)
})
```
__boom__: now you get numbers instead of strings.
Just as another example -- not saying this is a good idea -- let's say you want to return all dates from your database as [moment](http://momentjs.com/docs/) objects. Okay, do this:
```js
var types = require('pg').types
var moment = require('moment')
var parseFn = function(val) {
return val === null ? null : moment(val)
}
types.setTypeParser(types.builtins.TIMESTAMPTZ, parseFn)
types.setTypeParser(types.builtins.TIMESTAMP, parseFn)
```
_note: I've never done that with my dates, and I'm not 100% sure moment can parse all the date strings returned from postgres. It's just an example!_
If you're thinking "gee, this seems pretty handy, but how can I get a list of all the OIDs in the database and what they correspond to?!?!?!" worry not:
```bash
$ psql -c "select typname, oid, typarray from pg_type order by oid"
```
If you want to find out the OID of a specific type:
```bash
$ psql -c "select typname, oid, typarray from pg_type where typname = 'daterange' order by oid"
```
:smile:
## license
The MIT License (MIT)
Copyright (c) 2014 Brian M. Carlson
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.

View File

@@ -0,0 +1,79 @@
import type { Path, SourceFile } from "../ast/index.ts";
import type { SnapshotChanges } from "./proto.ts";
/**
* A cached source file entry, identified by content hash.
*/
export interface CachedSourceFile {
/** The cached source file object */
file: SourceFile;
/** The content hash from the server */
contentHash: string;
/** The parse options key that was used to create this file */
parseOptionsKey: string;
/** Set of (snapshot, project) ref keys that reference this entry */
refs: Set<string>;
}
/**
* Client-side cache for source files keyed by (path, parseOptionsKey, contentHash).
*
* Supports multiple versions of the same file at the same path (e.g., from
* different snapshots with different file contents). Each version is identified
* by its content hash and parse options key.
*
* Entries are ref-counted by (snapshot, project) pairs. When a snapshot is
* disposed, all refs for that snapshot across all projects are released,
* and entries with no remaining references are evicted.
*
* When a new snapshot is created, unchanged cache entries from the previous
* snapshot are retained per-project. Only files within changed or removed
* projects are invalidated.
*/
export declare class SourceFileCache {
/** Map from path to all cached versions of that file */
private cache;
/** Map from snapshotId to (projectId → Set of paths fetched through that project) */
private snapshotProjectPaths;
/**
* Get a cached source file already retained for the given (snapshot, project) pair.
* This does not require a content hash or parse options key — it returns the entry
* if one exists with a matching ref. Used to skip the server request entirely when
* retainForSnapshot has already carried over the ref.
*
* A given (snapshot, project) pair always parses a file the same way, so there is
* at most one matching entry per ref.
*/
getRetained(path: Path, snapshotId: number, projectId: string): SourceFile | undefined;
/**
* Store a source file in the cache and retain it for the given (snapshot, project) pair.
* Returns the cached file — which may be an existing entry if the hash matches.
*/
set(path: Path, file: SourceFile, parseOptionsKey: string, contentHash: string, snapshotId: number, projectId: string): SourceFile;
/**
* Retain cache entries from a previous snapshot for a new snapshot.
* For each project in the previous snapshot:
* - Removed projects: skip (don't retain any refs).
* - Changed projects: retain refs for files not listed in changedFiles/deletedFiles.
* - Unchanged projects: retain all refs.
*/
retainForSnapshot(newSnapshotId: number, previousSnapshotId: number, changes: SnapshotChanges | undefined): void;
/**
* Release all entries retained by the given snapshot across all projects.
* Only visits paths that the snapshot actually referenced.
* Entries with no remaining refs are evicted.
*/
releaseSnapshot(snapshotId: number): void;
private trackPath;
/**
* Clear all entries from the cache.
*/
clear(): void;
/**
* Get the number of unique paths in the cache.
*/
get size(): number;
/**
* Check if a path is in the cache.
*/
has(path: Path): boolean;
}
//# sourceMappingURL=sourceFileCache.d.ts.map

View File

@@ -0,0 +1,34 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isConditionalTest = isConditionalTest;
const utils_1 = require("@typescript-eslint/utils");
function isConditionalTest(node) {
const parent = node.parent;
if (parent == null) {
return false;
}
if (parent.type === utils_1.AST_NODE_TYPES.LogicalExpression) {
return isConditionalTest(parent);
}
if (parent.type === utils_1.AST_NODE_TYPES.ConditionalExpression &&
(parent.consequent === node || parent.alternate === node)) {
return isConditionalTest(parent);
}
if (parent.type === utils_1.AST_NODE_TYPES.SequenceExpression &&
parent.expressions.at(-1) === node) {
return isConditionalTest(parent);
}
if (parent.type === utils_1.AST_NODE_TYPES.UnaryExpression &&
parent.operator === '!') {
return isConditionalTest(parent);
}
if ((parent.type === utils_1.AST_NODE_TYPES.ConditionalExpression ||
parent.type === utils_1.AST_NODE_TYPES.DoWhileStatement ||
parent.type === utils_1.AST_NODE_TYPES.IfStatement ||
parent.type === utils_1.AST_NODE_TYPES.ForStatement ||
parent.type === utils_1.AST_NODE_TYPES.WhileStatement) &&
parent.test === node) {
return true;
}
return false;
}

View File

@@ -0,0 +1,13 @@
import * as ts from 'typescript';
import type { ParseSettings } from '../parseSettings';
/**
* Clear all of the parser caches.
* This should only be used in testing to ensure the parser is clean between tests.
*/
export declare function clearWatchCaches(): void;
/**
* Calculate project environments using options provided by consumer and paths from config
* @param parseSettings Internal settings for parsing the file
* @returns The programs corresponding to the supplied tsconfig paths
*/
export declare function getWatchProgramsForProjects(parseSettings: ParseSettings): ts.Program[];

View File

@@ -0,0 +1,35 @@
{
"name": "tinyrainbow",
"version": "3.1.1",
"packageManager": "pnpm@10.34.5",
"description": "A small library to print colourful messages.",
"type": "module",
"sideEffects": false,
"main": "./dist/index.js",
"module": "./dist/index.js",
"browser": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"files": [
"dist/**"
],
"repository": {
"type": "git",
"url": "git+https://github.com/tinylibs/tinyrainbow.git"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/tinylibs/tinyrainbow/issues"
},
"homepage": "https://github.com/tinylibs/tinyrainbow#readme",
"keywords": [
"colors",
"tty"
],
"engines": {
"node": ">=14.0.0"
}
}

View File

@@ -0,0 +1,42 @@
// @ts-ignore TS6133
import { expect, test } from "vitest";
import * as z from "zod/v3";
function checkErrors(a: z.ZodTypeAny, bad: any) {
let expected: any;
try {
a.parse(bad);
} catch (error) {
expected = (error as z.ZodError).formErrors;
}
try {
a.optional().parse(bad);
} catch (error) {
expect((error as z.ZodError).formErrors).toEqual(expected);
}
}
test("Should have error messages appropriate for the underlying type", () => {
checkErrors(z.string().min(2), 1);
z.string().min(2).optional().parse(undefined);
checkErrors(z.number().gte(2), 1);
z.number().gte(2).optional().parse(undefined);
checkErrors(z.boolean(), "");
z.boolean().optional().parse(undefined);
checkErrors(z.undefined(), null);
z.undefined().optional().parse(undefined);
checkErrors(z.null(), {});
z.null().optional().parse(undefined);
checkErrors(z.object({}), 1);
z.object({}).optional().parse(undefined);
checkErrors(z.tuple([]), 1);
z.tuple([]).optional().parse(undefined);
checkErrors(z.unknown(), 1);
z.unknown().optional().parse(undefined);
});
test("unwrap", () => {
const unwrapped = z.string().optional().unwrap();
expect(unwrapped).toBeInstanceOf(z.ZodString);
});

View File

@@ -0,0 +1,107 @@
import * as util from "../core/util.js";
const error = () => {
const Sizable = {
string: { unit: "caractères", verb: "avoir" },
file: { unit: "octets", verb: "avoir" },
array: { unit: "éléments", verb: "avoir" },
set: { unit: "éléments", verb: "avoir" },
};
function getSizing(origin) {
return Sizable[origin] ?? null;
}
const FormatDictionary = {
regex: "entrée",
email: "adresse courriel",
url: "URL",
emoji: "emoji",
uuid: "UUID",
uuidv4: "UUIDv4",
uuidv6: "UUIDv6",
nanoid: "nanoid",
guid: "GUID",
cuid: "cuid",
cuid2: "cuid2",
ulid: "ULID",
xid: "XID",
ksuid: "KSUID",
datetime: "date-heure ISO",
date: "date ISO",
time: "heure ISO",
duration: "durée ISO",
ipv4: "adresse IPv4",
ipv6: "adresse IPv6",
cidrv4: "plage IPv4",
cidrv6: "plage IPv6",
base64: "chaîne encodée en base64",
base64url: "chaîne encodée en base64url",
json_string: "chaîne JSON",
e164: "numéro E.164",
jwt: "JWT",
template_literal: "entrée",
};
const TypeDictionary = {
nan: "NaN",
};
return (issue) => {
switch (issue.code) {
case "invalid_type": {
const expected = TypeDictionary[issue.expected] ?? issue.expected;
const receivedType = util.parsedType(issue.input);
const received = TypeDictionary[receivedType] ?? receivedType;
if (/^[A-Z]/.test(issue.expected)) {
return `Entrée invalide : attendu instanceof ${issue.expected}, reçu ${received}`;
}
return `Entrée invalide : attendu ${expected}, reçu ${received}`;
}
case "invalid_value":
if (issue.values.length === 1)
return `Entrée invalide : attendu ${util.stringifyPrimitive(issue.values[0])}`;
return `Option invalide : attendu l'une des valeurs suivantes ${util.joinValues(issue.values, "|")}`;
case "too_big": {
const adj = issue.inclusive ? "≤" : "<";
const sizing = getSizing(issue.origin);
if (sizing)
return `Trop grand : attendu que ${issue.origin ?? "la valeur"} ait ${adj}${issue.maximum.toString()} ${sizing.unit}`;
return `Trop grand : attendu que ${issue.origin ?? "la valeur"} soit ${adj}${issue.maximum.toString()}`;
}
case "too_small": {
const adj = issue.inclusive ? "≥" : ">";
const sizing = getSizing(issue.origin);
if (sizing) {
return `Trop petit : attendu que ${issue.origin} ait ${adj}${issue.minimum.toString()} ${sizing.unit}`;
}
return `Trop petit : attendu que ${issue.origin} soit ${adj}${issue.minimum.toString()}`;
}
case "invalid_format": {
const _issue = issue;
if (_issue.format === "starts_with") {
return `Chaîne invalide : doit commencer par "${_issue.prefix}"`;
}
if (_issue.format === "ends_with")
return `Chaîne invalide : doit se terminer par "${_issue.suffix}"`;
if (_issue.format === "includes")
return `Chaîne invalide : doit inclure "${_issue.includes}"`;
if (_issue.format === "regex")
return `Chaîne invalide : doit correspondre au motif ${_issue.pattern}`;
return `${FormatDictionary[_issue.format] ?? issue.format} invalide`;
}
case "not_multiple_of":
return `Nombre invalide : doit être un multiple de ${issue.divisor}`;
case "unrecognized_keys":
return `Clé${issue.keys.length > 1 ? "s" : ""} non reconnue${issue.keys.length > 1 ? "s" : ""} : ${util.joinValues(issue.keys, ", ")}`;
case "invalid_key":
return `Clé invalide dans ${issue.origin}`;
case "invalid_union":
return "Entrée invalide";
case "invalid_element":
return `Valeur invalide dans ${issue.origin}`;
default:
return `Entrée invalide`;
}
};
};
export default function () {
return {
localeError: error(),
};
}

View File

@@ -0,0 +1,223 @@
const ANSI_BACKGROUND_OFFSET = 10;
const wrapAnsi16 = (offset = 0) => code => `\u001B[${code + offset}m`;
const wrapAnsi256 = (offset = 0) => code => `\u001B[${38 + offset};5;${code}m`;
const wrapAnsi16m = (offset = 0) => (red, green, blue) => `\u001B[${38 + offset};2;${red};${green};${blue}m`;
const styles = {
modifier: {
reset: [0, 0],
// 21 isn't widely supported and 22 does the same thing
bold: [1, 22],
dim: [2, 22],
italic: [3, 23],
underline: [4, 24],
overline: [53, 55],
inverse: [7, 27],
hidden: [8, 28],
strikethrough: [9, 29],
},
color: {
black: [30, 39],
red: [31, 39],
green: [32, 39],
yellow: [33, 39],
blue: [34, 39],
magenta: [35, 39],
cyan: [36, 39],
white: [37, 39],
// Bright color
blackBright: [90, 39],
gray: [90, 39], // Alias of `blackBright`
grey: [90, 39], // Alias of `blackBright`
redBright: [91, 39],
greenBright: [92, 39],
yellowBright: [93, 39],
blueBright: [94, 39],
magentaBright: [95, 39],
cyanBright: [96, 39],
whiteBright: [97, 39],
},
bgColor: {
bgBlack: [40, 49],
bgRed: [41, 49],
bgGreen: [42, 49],
bgYellow: [43, 49],
bgBlue: [44, 49],
bgMagenta: [45, 49],
bgCyan: [46, 49],
bgWhite: [47, 49],
// Bright color
bgBlackBright: [100, 49],
bgGray: [100, 49], // Alias of `bgBlackBright`
bgGrey: [100, 49], // Alias of `bgBlackBright`
bgRedBright: [101, 49],
bgGreenBright: [102, 49],
bgYellowBright: [103, 49],
bgBlueBright: [104, 49],
bgMagentaBright: [105, 49],
bgCyanBright: [106, 49],
bgWhiteBright: [107, 49],
},
};
export const modifierNames = Object.keys(styles.modifier);
export const foregroundColorNames = Object.keys(styles.color);
export const backgroundColorNames = Object.keys(styles.bgColor);
export const colorNames = [...foregroundColorNames, ...backgroundColorNames];
function assembleStyles() {
const codes = new Map();
for (const [groupName, group] of Object.entries(styles)) {
for (const [styleName, style] of Object.entries(group)) {
styles[styleName] = {
open: `\u001B[${style[0]}m`,
close: `\u001B[${style[1]}m`,
};
group[styleName] = styles[styleName];
codes.set(style[0], style[1]);
}
Object.defineProperty(styles, groupName, {
value: group,
enumerable: false,
});
}
Object.defineProperty(styles, 'codes', {
value: codes,
enumerable: false,
});
styles.color.close = '\u001B[39m';
styles.bgColor.close = '\u001B[49m';
styles.color.ansi = wrapAnsi16();
styles.color.ansi256 = wrapAnsi256();
styles.color.ansi16m = wrapAnsi16m();
styles.bgColor.ansi = wrapAnsi16(ANSI_BACKGROUND_OFFSET);
styles.bgColor.ansi256 = wrapAnsi256(ANSI_BACKGROUND_OFFSET);
styles.bgColor.ansi16m = wrapAnsi16m(ANSI_BACKGROUND_OFFSET);
// From https://github.com/Qix-/color-convert/blob/3f0e0d4e92e235796ccb17f6e85c72094a651f49/conversions.js
Object.defineProperties(styles, {
rgbToAnsi256: {
value(red, green, blue) {
// We use the extended greyscale palette here, with the exception of
// black and white. normal palette only has 4 greyscale shades.
if (red === green && green === blue) {
if (red < 8) {
return 16;
}
if (red > 248) {
return 231;
}
return Math.round(((red - 8) / 247) * 24) + 232;
}
return 16
+ (36 * Math.round(red / 255 * 5))
+ (6 * Math.round(green / 255 * 5))
+ Math.round(blue / 255 * 5);
},
enumerable: false,
},
hexToRgb: {
value(hex) {
const matches = /[a-f\d]{6}|[a-f\d]{3}/i.exec(hex.toString(16));
if (!matches) {
return [0, 0, 0];
}
let [colorString] = matches;
if (colorString.length === 3) {
colorString = [...colorString].map(character => character + character).join('');
}
const integer = Number.parseInt(colorString, 16);
return [
/* eslint-disable no-bitwise */
(integer >> 16) & 0xFF,
(integer >> 8) & 0xFF,
integer & 0xFF,
/* eslint-enable no-bitwise */
];
},
enumerable: false,
},
hexToAnsi256: {
value: hex => styles.rgbToAnsi256(...styles.hexToRgb(hex)),
enumerable: false,
},
ansi256ToAnsi: {
value(code) {
if (code < 8) {
return 30 + code;
}
if (code < 16) {
return 90 + (code - 8);
}
let red;
let green;
let blue;
if (code >= 232) {
red = (((code - 232) * 10) + 8) / 255;
green = red;
blue = red;
} else {
code -= 16;
const remainder = code % 36;
red = Math.floor(code / 36) / 5;
green = Math.floor(remainder / 6) / 5;
blue = (remainder % 6) / 5;
}
const value = Math.max(red, green, blue) * 2;
if (value === 0) {
return 30;
}
// eslint-disable-next-line no-bitwise
let result = 30 + ((Math.round(blue) << 2) | (Math.round(green) << 1) | Math.round(red));
if (value === 2) {
result += 60;
}
return result;
},
enumerable: false,
},
rgbToAnsi: {
value: (red, green, blue) => styles.ansi256ToAnsi(styles.rgbToAnsi256(red, green, blue)),
enumerable: false,
},
hexToAnsi: {
value: hex => styles.ansi256ToAnsi(styles.hexToAnsi256(hex)),
enumerable: false,
},
});
return styles;
}
const ansiStyles = assembleStyles();
export default ansiStyles;

View File

@@ -0,0 +1,16 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _v = _interopRequireDefault(require("./v35.js"));
var _sha = _interopRequireDefault(require("./sha1.js"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
const v5 = (0, _v.default)('v5', 0x50, _sha.default);
var _default = v5;
exports.default = _default;

View File

@@ -0,0 +1,15 @@
"use strict";
// THIS CODE WAS AUTOMATICALLY GENERATED
// DO NOT EDIT THIS CODE BY HAND
// RUN THE FOLLOWING COMMAND FROM THE WORKSPACE ROOT TO REGENERATE:
// npx nx generate-lib repo
Object.defineProperty(exports, "__esModule", { value: true });
exports.esnext_typedarrays = void 0;
const base_config_1 = require("./base-config");
exports.esnext_typedarrays = {
libs: [],
variables: [
['Uint8Array', base_config_1.TYPE],
['Uint8ArrayConstructor', base_config_1.TYPE],
],
};

View File

@@ -0,0 +1,12 @@
import type { TSESLint } from '@typescript-eslint/utils';
import type { TypeOrValueSpecifier } from '../util';
type Options = [
{
allow?: TypeOrValueSpecifier[];
}
];
type MessageIds = 'addAwait' | 'noArraySpreadInObject' | 'noClassDeclarationSpreadInObject' | 'noClassInstanceSpreadInObject' | 'noFunctionSpreadInObject' | 'noIterableSpreadInObject' | 'noMapSpreadInObject' | 'noPromiseSpreadInObject' | 'noStringSpread' | 'replaceMapSpreadInObject';
declare const _default: TSESLint.RuleModule<MessageIds, Options, import("../../rules").ESLintPluginDocs, TSESLint.RuleListener> & {
name: string;
};
export default _default;

View File

@@ -0,0 +1,424 @@
/**
* @fileoverview Rule to forbid or enforce dangling commas.
* @author Ian Christian Myers
* @deprecated in ESLint v8.53.0
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("./utils/ast-utils");
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
const DEFAULT_OPTIONS = Object.freeze({
arrays: "never",
objects: "never",
imports: "never",
exports: "never",
functions: "never",
});
/**
* Checks whether or not a trailing comma is allowed in a given node.
* If the `lastItem` is `RestElement` or `RestProperty`, it disallows trailing commas.
* @param {ASTNode} lastItem The node of the last element in the given node.
* @returns {boolean} `true` if a trailing comma is allowed.
*/
function isTrailingCommaAllowed(lastItem) {
return !(
lastItem.type === "RestElement" ||
lastItem.type === "RestProperty" ||
lastItem.type === "ExperimentalRestProperty"
);
}
/**
* Normalize option value.
* @param {string|Object|undefined} optionValue The 1st option value to normalize.
* @param {number} ecmaVersion The normalized ECMAScript version.
* @returns {Object} The normalized option value.
*/
function normalizeOptions(optionValue, ecmaVersion) {
if (typeof optionValue === "string") {
return {
arrays: optionValue,
objects: optionValue,
imports: optionValue,
exports: optionValue,
functions: ecmaVersion < 2017 ? "ignore" : optionValue,
};
}
if (typeof optionValue === "object" && optionValue !== null) {
return {
arrays: optionValue.arrays || DEFAULT_OPTIONS.arrays,
objects: optionValue.objects || DEFAULT_OPTIONS.objects,
imports: optionValue.imports || DEFAULT_OPTIONS.imports,
exports: optionValue.exports || DEFAULT_OPTIONS.exports,
functions: optionValue.functions || DEFAULT_OPTIONS.functions,
};
}
return DEFAULT_OPTIONS;
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../types').Rule.RuleModule} */
module.exports = {
meta: {
deprecated: {
message: "Formatting rules are being moved out of ESLint core.",
url: "https://eslint.org/blog/2023/10/deprecating-formatting-rules/",
deprecatedSince: "8.53.0",
availableUntil: "11.0.0",
replacedBy: [
{
message:
"ESLint Stylistic now maintains deprecated stylistic core rules.",
url: "https://eslint.style/guide/migration",
plugin: {
name: "@stylistic/eslint-plugin",
url: "https://eslint.style",
},
rule: {
name: "comma-dangle",
url: "https://eslint.style/rules/comma-dangle",
},
},
],
},
type: "layout",
docs: {
description: "Require or disallow trailing commas",
recommended: false,
url: "https://eslint.org/docs/latest/rules/comma-dangle",
},
fixable: "code",
schema: {
definitions: {
value: {
enum: [
"always-multiline",
"always",
"never",
"only-multiline",
],
},
valueWithIgnore: {
enum: [
"always-multiline",
"always",
"ignore",
"never",
"only-multiline",
],
},
},
type: "array",
items: [
{
oneOf: [
{
$ref: "#/definitions/value",
},
{
type: "object",
properties: {
arrays: {
$ref: "#/definitions/valueWithIgnore",
},
objects: {
$ref: "#/definitions/valueWithIgnore",
},
imports: {
$ref: "#/definitions/valueWithIgnore",
},
exports: {
$ref: "#/definitions/valueWithIgnore",
},
functions: {
$ref: "#/definitions/valueWithIgnore",
},
},
additionalProperties: false,
},
],
},
],
additionalItems: false,
},
messages: {
unexpected: "Unexpected trailing comma.",
missing: "Missing trailing comma.",
},
},
create(context) {
const options = normalizeOptions(
context.options[0],
context.languageOptions.ecmaVersion,
);
const sourceCode = context.sourceCode;
/**
* Gets the last item of the given node.
* @param {ASTNode} node The node to get.
* @returns {ASTNode|null} The last node or null.
*/
function getLastItem(node) {
/**
* Returns the last element of an array
* @param {any[]} array The input array
* @returns {any} The last element
*/
function last(array) {
return array.at(-1);
}
switch (node.type) {
case "ObjectExpression":
case "ObjectPattern":
return last(node.properties);
case "ArrayExpression":
case "ArrayPattern":
return last(node.elements);
case "ImportDeclaration":
case "ExportNamedDeclaration":
return last(node.specifiers);
case "FunctionDeclaration":
case "FunctionExpression":
case "ArrowFunctionExpression":
return last(node.params);
case "CallExpression":
case "NewExpression":
return last(node.arguments);
default:
return null;
}
}
/**
* Gets the trailing comma token of the given node.
* If the trailing comma does not exist, this returns the token which is
* the insertion point of the trailing comma token.
* @param {ASTNode} node The node to get.
* @param {ASTNode} lastItem The last item of the node.
* @returns {Token} The trailing comma token or the insertion point.
*/
function getTrailingToken(node, lastItem) {
switch (node.type) {
case "ObjectExpression":
case "ArrayExpression":
case "CallExpression":
case "NewExpression":
return sourceCode.getLastToken(node, 1);
default: {
const nextToken = sourceCode.getTokenAfter(lastItem);
if (astUtils.isCommaToken(nextToken)) {
return nextToken;
}
return sourceCode.getLastToken(lastItem);
}
}
}
/**
* Checks whether or not a given node is multiline.
* This rule handles a given node as multiline when the closing parenthesis
* and the last element are not on the same line.
* @param {ASTNode} node A node to check.
* @returns {boolean} `true` if the node is multiline.
*/
function isMultiline(node) {
const lastItem = getLastItem(node);
if (!lastItem) {
return false;
}
const penultimateToken = getTrailingToken(node, lastItem);
const lastToken = sourceCode.getTokenAfter(penultimateToken);
return lastToken.loc.end.line !== penultimateToken.loc.end.line;
}
/**
* Reports a trailing comma if it exists.
* @param {ASTNode} node A node to check. Its type is one of
* ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
* ImportDeclaration, and ExportNamedDeclaration.
* @returns {void}
*/
function forbidTrailingComma(node) {
const lastItem = getLastItem(node);
if (
!lastItem ||
(node.type === "ImportDeclaration" &&
lastItem.type !== "ImportSpecifier")
) {
return;
}
const trailingToken = getTrailingToken(node, lastItem);
if (astUtils.isCommaToken(trailingToken)) {
context.report({
node: lastItem,
loc: trailingToken.loc,
messageId: "unexpected",
*fix(fixer) {
yield fixer.remove(trailingToken);
/*
* Extend the range of the fix to include surrounding tokens to ensure
* that the element after which the comma is removed stays _last_.
* This intentionally makes conflicts in fix ranges with rules that may be
* adding or removing elements in the same autofix pass.
* https://github.com/eslint/eslint/issues/15660
*/
yield fixer.insertTextBefore(
sourceCode.getTokenBefore(trailingToken),
"",
);
yield fixer.insertTextAfter(
sourceCode.getTokenAfter(trailingToken),
"",
);
},
});
}
}
/**
* Reports the last element of a given node if it does not have a trailing
* comma.
*
* If a given node is `ArrayPattern` which has `RestElement`, the trailing
* comma is disallowed, so report if it exists.
* @param {ASTNode} node A node to check. Its type is one of
* ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
* ImportDeclaration, and ExportNamedDeclaration.
* @returns {void}
*/
function forceTrailingComma(node) {
const lastItem = getLastItem(node);
if (
!lastItem ||
(node.type === "ImportDeclaration" &&
lastItem.type !== "ImportSpecifier")
) {
return;
}
if (!isTrailingCommaAllowed(lastItem)) {
forbidTrailingComma(node);
return;
}
const trailingToken = getTrailingToken(node, lastItem);
if (trailingToken.value !== ",") {
context.report({
node: lastItem,
loc: {
start: trailingToken.loc.end,
end: astUtils.getNextLocation(
sourceCode,
trailingToken.loc.end,
),
},
messageId: "missing",
*fix(fixer) {
yield fixer.insertTextAfter(trailingToken, ",");
/*
* Extend the range of the fix to include surrounding tokens to ensure
* that the element after which the comma is inserted stays _last_.
* This intentionally makes conflicts in fix ranges with rules that may be
* adding or removing elements in the same autofix pass.
* https://github.com/eslint/eslint/issues/15660
*/
yield fixer.insertTextBefore(trailingToken, "");
yield fixer.insertTextAfter(
sourceCode.getTokenAfter(trailingToken),
"",
);
},
});
}
}
/**
* If a given node is multiline, reports the last element of a given node
* when it does not have a trailing comma.
* Otherwise, reports a trailing comma if it exists.
* @param {ASTNode} node A node to check. Its type is one of
* ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
* ImportDeclaration, and ExportNamedDeclaration.
* @returns {void}
*/
function forceTrailingCommaIfMultiline(node) {
if (isMultiline(node)) {
forceTrailingComma(node);
} else {
forbidTrailingComma(node);
}
}
/**
* Only if a given node is not multiline, reports the last element of a given node
* when it does not have a trailing comma.
* Otherwise, reports a trailing comma if it exists.
* @param {ASTNode} node A node to check. Its type is one of
* ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
* ImportDeclaration, and ExportNamedDeclaration.
* @returns {void}
*/
function allowTrailingCommaIfMultiline(node) {
if (!isMultiline(node)) {
forbidTrailingComma(node);
}
}
const predicate = {
always: forceTrailingComma,
"always-multiline": forceTrailingCommaIfMultiline,
"only-multiline": allowTrailingCommaIfMultiline,
never: forbidTrailingComma,
ignore() {},
};
return {
ObjectExpression: predicate[options.objects],
ObjectPattern: predicate[options.objects],
ArrayExpression: predicate[options.arrays],
ArrayPattern: predicate[options.arrays],
ImportDeclaration: predicate[options.imports],
ExportNamedDeclaration: predicate[options.exports],
FunctionDeclaration: predicate[options.functions],
FunctionExpression: predicate[options.functions],
ArrowFunctionExpression: predicate[options.functions],
CallExpression: predicate[options.functions],
NewExpression: predicate[options.functions],
};
},
};

View File

@@ -0,0 +1,21 @@
import type { TSESLint } from '@typescript-eslint/utils';
export type Options = [
{
checksConditionals?: boolean;
checksSpreads?: boolean;
checksVoidReturn?: boolean | ChecksVoidReturnOptions;
}
];
export interface ChecksVoidReturnOptions {
arguments?: boolean;
attributes?: boolean;
inheritedMethods?: boolean;
properties?: boolean;
returns?: boolean;
variables?: boolean;
}
export type MessageId = 'conditional' | 'predicate' | 'spread' | 'voidReturnArgument' | 'voidReturnAttribute' | 'voidReturnInheritedMethod' | 'voidReturnProperty' | 'voidReturnReturnValue' | 'voidReturnVariable';
declare const _default: TSESLint.RuleModule<MessageId, Options, import("../../rules").ESLintPluginDocs, TSESLint.RuleListener> & {
name: string;
};
export default _default;

View File

@@ -0,0 +1,102 @@
/**
* @fileoverview Rule to flag use of unary increment and decrement operators.
* @author Ian Christian Myers
* @author Brody McKee (github.com/mrmckeb)
*/
"use strict";
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* Determines whether the given node is the update node of a `ForStatement`.
* @param {ASTNode} node The node to check.
* @returns {boolean} `true` if the node is `ForStatement` update.
*/
function isForStatementUpdate(node) {
const parent = node.parent;
return parent.type === "ForStatement" && parent.update === node;
}
/**
* Determines whether the given node is considered to be a for loop "afterthought" by the logic of this rule.
* In particular, it returns `true` if the given node is either:
* - The update node of a `ForStatement`: for (;; i++) {}
* - An operand of a sequence expression that is the update node: for (;; foo(), i++) {}
* - An operand of a sequence expression that is child of another sequence expression, etc.,
* up to the sequence expression that is the update node: for (;; foo(), (bar(), (baz(), i++))) {}
* @param {ASTNode} node The node to check.
* @returns {boolean} `true` if the node is a for loop afterthought.
*/
function isForLoopAfterthought(node) {
const parent = node.parent;
if (parent.type === "SequenceExpression") {
return isForLoopAfterthought(parent);
}
return isForStatementUpdate(node);
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../types').Rule.RuleModule} */
module.exports = {
meta: {
type: "suggestion",
defaultOptions: [
{
allowForLoopAfterthoughts: false,
},
],
docs: {
description: "Disallow the unary operators `++` and `--`",
recommended: false,
frozen: true,
url: "https://eslint.org/docs/latest/rules/no-plusplus",
},
schema: [
{
type: "object",
properties: {
allowForLoopAfterthoughts: {
type: "boolean",
},
},
additionalProperties: false,
},
],
messages: {
unexpectedUnaryOp: "Unary operator '{{operator}}' used.",
},
},
create(context) {
const [{ allowForLoopAfterthoughts }] = context.options;
return {
UpdateExpression(node) {
if (allowForLoopAfterthoughts && isForLoopAfterthought(node)) {
return;
}
context.report({
node,
messageId: "unexpectedUnaryOp",
data: {
operator: node.operator,
},
});
},
};
},
};

View File

@@ -0,0 +1,21 @@
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
/// <reference no-default-lib="true"/>
interface WeakKeyTypes {
symbol: symbol;
}

View File

@@ -0,0 +1,67 @@
/**
* @fileoverview A shared list of ES3 keywords.
* @author Josh Perez
*/
"use strict";
module.exports = [
"abstract",
"boolean",
"break",
"byte",
"case",
"catch",
"char",
"class",
"const",
"continue",
"debugger",
"default",
"delete",
"do",
"double",
"else",
"enum",
"export",
"extends",
"false",
"final",
"finally",
"float",
"for",
"function",
"goto",
"if",
"implements",
"import",
"in",
"instanceof",
"int",
"interface",
"long",
"native",
"new",
"null",
"package",
"private",
"protected",
"public",
"return",
"short",
"static",
"super",
"switch",
"synchronized",
"this",
"throw",
"throws",
"transient",
"true",
"try",
"typeof",
"var",
"void",
"volatile",
"while",
"with",
];

View File

@@ -0,0 +1,4 @@
import type { Version6Options } from './types.js';
declare function v6(options?: Version6Options, buf?: undefined, offset?: number): string;
declare function v6<TBuf extends Uint8Array = Uint8Array>(options: Version6Options | undefined, buf: TBuf, offset?: number): TBuf;
export default v6;

View File

@@ -0,0 +1,5 @@
import REGEX from './regex.js';
function validate(uuid) {
return typeof uuid === 'string' && REGEX.test(uuid);
}
export default validate;

View File

@@ -0,0 +1,658 @@
/**
* @fileoverview A rule to disallow unnecessary assignments`.
* @author Yosuke Ota
*/
"use strict";
const { findVariable } = require("@eslint-community/eslint-utils");
//------------------------------------------------------------------------------
// Types
//------------------------------------------------------------------------------
/** @typedef {import("estree").Node} ASTNode */
/** @typedef {import("estree").Pattern} Pattern */
/** @typedef {import("estree").Identifier} Identifier */
/** @typedef {import("estree").VariableDeclarator} VariableDeclarator */
/** @typedef {import("estree").AssignmentExpression} AssignmentExpression */
/** @typedef {import("estree").UpdateExpression} UpdateExpression */
/** @typedef {import("estree").Expression} Expression */
/** @typedef {import("eslint-scope").Scope} Scope */
/** @typedef {import("eslint-scope").Variable} Variable */
/** @typedef {import("../linter/code-path-analysis/code-path")} CodePath */
/** @typedef {import("../linter/code-path-analysis/code-path-segment")} CodePathSegment */
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* Extract identifier from the given pattern node used on the left-hand side of the assignment.
* @param {Pattern} pattern The pattern node to extract identifier
* @returns {Iterable<Identifier>} The extracted identifier
*/
function* extractIdentifiersFromPattern(pattern) {
switch (pattern.type) {
case "Identifier":
yield pattern;
return;
case "ObjectPattern":
for (const property of pattern.properties) {
yield* extractIdentifiersFromPattern(
property.type === "Property" ? property.value : property,
);
}
return;
case "ArrayPattern":
for (const element of pattern.elements) {
if (!element) {
continue;
}
yield* extractIdentifiersFromPattern(element);
}
return;
case "RestElement":
yield* extractIdentifiersFromPattern(pattern.argument);
return;
case "AssignmentPattern":
yield* extractIdentifiersFromPattern(pattern.left);
// no default
}
}
/**
* Checks whether the given identifier node is evaluated after the assignment identifier.
* @param {AssignmentInfo} assignment The assignment info.
* @param {Identifier} identifier The identifier to check.
* @returns {boolean} `true` if the given identifier node is evaluated after the assignment identifier.
*/
function isIdentifierEvaluatedAfterAssignment(assignment, identifier) {
if (identifier.range[0] < assignment.identifier.range[1]) {
return false;
}
if (
assignment.expression &&
assignment.expression.range[0] <= identifier.range[0] &&
identifier.range[1] <= assignment.expression.range[1]
) {
/*
* The identifier node is in an expression that is evaluated before the assignment.
* e.g. x = id;
* ^^ identifier to check
* ^ assignment identifier
*/
return false;
}
/*
* e.g.
* x = 42; id;
* ^^ identifier to check
* ^ assignment identifier
* let { x, y = id } = obj;
* ^^ identifier to check
* ^ assignment identifier
*/
return true;
}
/**
* Checks whether the given identifier node is used between the assigned identifier and the equal sign.
*
* e.g. let { x, y = x } = obj;
* ^ identifier to check
* ^ assigned identifier
* @param {AssignmentInfo} assignment The assignment info.
* @param {Identifier} identifier The identifier to check.
* @returns {boolean} `true` if the given identifier node is used between the assigned identifier and the equal sign.
*/
function isIdentifierUsedBetweenAssignedAndEqualSign(assignment, identifier) {
if (!assignment.expression) {
return false;
}
return (
assignment.identifier.range[1] <= identifier.range[0] &&
identifier.range[1] <= assignment.expression.range[0]
);
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../types').Rule.RuleModule} */
module.exports = {
meta: {
type: "problem",
docs: {
description:
"Disallow variable assignments when the value is not used",
recommended: true,
url: "https://eslint.org/docs/latest/rules/no-useless-assignment",
},
schema: [],
messages: {
unnecessaryAssignment:
"The value assigned to '{{name}}' is not used in subsequent statements.",
},
},
create(context) {
const sourceCode = context.sourceCode;
/**
* @typedef {Object} ScopeStack
* @property {CodePath} codePath The code path of this scope stack.
* @property {Scope} scope The scope of this scope stack.
* @property {ScopeStack} upper The upper scope stack.
* @property {Record<string, ScopeStackSegmentInfo>} segments The map of ScopeStackSegmentInfo.
* @property {Set<CodePathSegment>} currentSegments The current CodePathSegments.
* @property {Map<Variable, AssignmentInfo[]>} assignments The map of list of AssignmentInfo for each variable.
* @property {Array} tryStatementBlocks The array of TryStatement block nodes in this scope stack.
*/
/**
* @typedef {Object} ScopeStackSegmentInfo
* @property {CodePathSegment} segment The code path segment.
* @property {Identifier|null} first The first identifier that appears within the segment.
* @property {Identifier|null} last The last identifier that appears within the segment.
* `first` and `last` are used to determine whether an identifier exists within the segment position range.
* Since it is used as a range of segments, we should originally hold all nodes, not just identifiers,
* but since the only nodes to be judged are identifiers, it is sufficient to have a range of identifiers.
*/
/**
* @typedef {Object} AssignmentInfo
* @property {Variable} variable The variable that is assigned.
* @property {Identifier} identifier The identifier that is assigned.
* @property {VariableDeclarator|AssignmentExpression|UpdateExpression} node The node where the variable was updated.
* @property {Expression|null} expression The expression that is evaluated before the assignment.
* @property {CodePathSegment[]} segments The code path segments where the assignment was made.
*/
/** @type {ScopeStack} */
let scopeStack = null;
/** @type {Set<Scope>} */
const codePathStartScopes = new Set();
/**
* Gets the scope of code path start from given scope
* @param {Scope} scope The initial scope
* @returns {Scope} The scope of code path start
* @throws {Error} Unexpected error
*/
function getCodePathStartScope(scope) {
let target = scope;
while (target) {
if (codePathStartScopes.has(target)) {
return target;
}
target = target.upper;
}
// Should be unreachable
return null;
}
/**
* Verify the given scope stack.
* @param {ScopeStack} target The scope stack to verify.
* @returns {void}
*/
function verify(target) {
/**
* Checks whether the given identifier is used in the segment.
* @param {CodePathSegment} segment The code path segment.
* @param {Identifier} identifier The identifier to check.
* @returns {boolean} `true` if the identifier is used in the segment.
*/
function isIdentifierUsedInSegment(segment, identifier) {
const segmentInfo = target.segments[segment.id];
return (
segmentInfo.first &&
segmentInfo.last &&
segmentInfo.first.range[0] <= identifier.range[0] &&
identifier.range[1] <= segmentInfo.last.range[1]
);
}
/**
* Verifies whether the given assignment info is an used assignment.
* Report if it is an unused assignment.
* @param {AssignmentInfo} targetAssignment The assignment info to verify.
* @param {AssignmentInfo[]} allAssignments The list of all assignment info for variables.
* @returns {void}
*/
function verifyAssignmentIsUsed(targetAssignment, allAssignments) {
// Skip assignment if it is in a try block.
const isAssignmentInTryBlock = target.tryStatementBlocks.some(
tryBlock =>
tryBlock.range[0] <=
targetAssignment.identifier.range[0] &&
targetAssignment.identifier.range[1] <=
tryBlock.range[1],
);
if (isAssignmentInTryBlock) {
return;
}
/**
* @typedef {Object} SubsequentSegmentData
* @property {CodePathSegment} segment The code path segment
* @property {AssignmentInfo} [assignment] The first occurrence of the assignment within the segment.
* There is no need to check if the variable is used after this assignment,
* as the value it was assigned will be used.
*/
/**
* Information used in `getSubsequentSegments()`.
* To avoid unnecessary iterations, cache information that has already been iterated over,
* and if additional iterations are needed, start iterating from the retained position.
*/
const subsequentSegmentData = {
/**
* Cache of subsequent segment information list that have already been iterated.
* @type {SubsequentSegmentData[]}
*/
results: [],
/**
* Subsequent segments that have already been iterated on. Used to avoid infinite loops.
* @type {Set<CodePathSegment>}
*/
subsequentSegments: new Set(),
/**
* Unexplored code path segment.
* If additional iterations are needed, consume this information and iterate.
* @type {CodePathSegment[]}
*/
queueSegments: targetAssignment.segments.flatMap(
segment => segment.nextSegments,
),
};
/**
* Gets the subsequent segments from the segment of
* the assignment currently being validated (targetAssignment).
* @returns {Iterable<SubsequentSegmentData>} the subsequent segments
*/
function* getSubsequentSegments() {
yield* subsequentSegmentData.results;
while (subsequentSegmentData.queueSegments.length > 0) {
const nextSegment =
subsequentSegmentData.queueSegments.shift();
if (
subsequentSegmentData.subsequentSegments.has(
nextSegment,
)
) {
continue;
}
subsequentSegmentData.subsequentSegments.add(
nextSegment,
);
const assignmentInSegment = allAssignments.find(
otherAssignment =>
otherAssignment.segments.includes(
nextSegment,
) &&
!isIdentifierUsedBetweenAssignedAndEqualSign(
otherAssignment,
targetAssignment.identifier,
),
);
if (!assignmentInSegment) {
/*
* Stores the next segment to explore.
* If `assignmentInSegment` exists,
* we are guarding it because we don't need to explore the next segment.
*/
subsequentSegmentData.queueSegments.push(
...nextSegment.nextSegments,
);
}
/** @type {SubsequentSegmentData} */
const result = {
segment: nextSegment,
assignment: assignmentInSegment,
};
subsequentSegmentData.results.push(result);
yield result;
}
}
if (
targetAssignment.variable.references.some(ref => {
const type = ref.identifier.type;
return (
type !== "Identifier" && type !== "JSXIdentifier"
);
})
) {
/**
* Skip checking for a variable that has at least one non-identifier reference.
* It's generated by plugins and cannot be handled reliably in the core rule.
*/
return;
}
const readReferences =
targetAssignment.variable.references.filter(reference =>
reference.isRead(),
);
if (!readReferences.length) {
/*
* It is not just an unnecessary assignment, but an unnecessary (unused) variable
* and thus should not be reported by this rule because it is reported by `no-unused-vars`.
*/
return;
}
/**
* Other assignment on the current segment and after current assignment.
*/
const otherAssignmentAfterTargetAssignment =
allAssignments.find(assignment => {
if (
assignment === targetAssignment ||
(assignment.segments.length &&
assignment.segments.every(
segment =>
!targetAssignment.segments.includes(
segment,
),
))
) {
return false;
}
if (
isIdentifierEvaluatedAfterAssignment(
targetAssignment,
assignment.identifier,
)
) {
return true;
}
if (
assignment.expression &&
assignment.expression.range[0] <=
targetAssignment.identifier.range[0] &&
targetAssignment.identifier.range[1] <=
assignment.expression.range[1]
) {
/*
* The target assignment is in an expression that is evaluated before the assignment.
* e.g. x=(x=1);
* ^^^ targetAssignment
* ^^^^^^^ assignment
*/
return true;
}
return false;
});
for (const reference of readReferences) {
/*
* If the scope of the reference is outside the current code path scope,
* we cannot track whether this assignment is not used.
* For example, it can also be called asynchronously.
*/
if (
target.scope !== getCodePathStartScope(reference.from)
) {
return;
}
// Checks if it is used in the same segment as the target assignment.
if (
isIdentifierEvaluatedAfterAssignment(
targetAssignment,
reference.identifier,
) &&
(isIdentifierUsedBetweenAssignedAndEqualSign(
targetAssignment,
reference.identifier,
) ||
targetAssignment.segments.some(segment =>
isIdentifierUsedInSegment(
segment,
reference.identifier,
),
))
) {
if (
otherAssignmentAfterTargetAssignment &&
isIdentifierEvaluatedAfterAssignment(
otherAssignmentAfterTargetAssignment,
reference.identifier,
)
) {
// There was another assignment before the reference. Therefore, it has not been used yet.
continue;
}
// Uses in statements after the written identifier.
return;
}
if (otherAssignmentAfterTargetAssignment) {
/*
* The assignment was followed by another assignment in the same segment.
* Therefore, there is no need to check the next segment.
*/
continue;
}
// Check subsequent segments.
for (const subsequentSegment of getSubsequentSegments()) {
if (
isIdentifierUsedInSegment(
subsequentSegment.segment,
reference.identifier,
)
) {
if (
subsequentSegment.assignment &&
isIdentifierEvaluatedAfterAssignment(
subsequentSegment.assignment,
reference.identifier,
)
) {
// There was another assignment before the reference. Therefore, it has not been used yet.
continue;
}
// It is used
return;
}
}
}
context.report({
node: targetAssignment.identifier,
messageId: "unnecessaryAssignment",
data: { name: targetAssignment.identifier.name },
});
}
// Verify that each assignment in the code path is used.
for (const assignments of target.assignments.values()) {
assignments.sort(
(a, b) => a.identifier.range[0] - b.identifier.range[0],
);
for (const assignment of assignments) {
verifyAssignmentIsUsed(assignment, assignments);
}
}
}
return {
onCodePathStart(codePath, node) {
const scope = sourceCode.getScope(node);
scopeStack = {
upper: scopeStack,
codePath,
scope,
segments: Object.create(null),
currentSegments: new Set(),
assignments: new Map(),
tryStatementBlocks: [],
};
codePathStartScopes.add(scopeStack.scope);
},
onCodePathEnd() {
verify(scopeStack);
scopeStack = scopeStack.upper;
},
onCodePathSegmentStart(segment) {
const segmentInfo = { segment, first: null, last: null };
scopeStack.segments[segment.id] = segmentInfo;
scopeStack.currentSegments.add(segment);
},
onCodePathSegmentEnd(segment) {
scopeStack.currentSegments.delete(segment);
},
TryStatement(node) {
scopeStack.tryStatementBlocks.push(node.block);
},
"Identifier, JSXIdentifier"(node) {
for (const segment of scopeStack.currentSegments) {
const segmentInfo = scopeStack.segments[segment.id];
if (!segmentInfo.first) {
segmentInfo.first = node;
}
segmentInfo.last = node;
}
},
"VariableDeclarator[init!=null], AssignmentExpression, UpdateExpression:exit"(
node,
) {
if (scopeStack.currentSegments.size === 0) {
// Ignore unreachable segments
return;
}
const assignments = scopeStack.assignments;
let pattern;
let expression = null;
if (node.type === "VariableDeclarator") {
pattern = node.id;
expression = node.init;
} else if (node.type === "AssignmentExpression") {
pattern = node.left;
expression = node.right;
} else {
// UpdateExpression
pattern = node.argument;
}
for (const identifier of extractIdentifiersFromPattern(
pattern,
)) {
const scope = sourceCode.getScope(identifier);
/** @type {Variable} */
const variable = findVariable(scope, identifier);
if (!variable) {
continue;
}
// We don't know where global variables are used.
if (
variable.scope.type === "global" &&
variable.defs.length === 0
) {
continue;
}
/*
* If the scope of the variable is outside the current code path scope,
* we cannot track whether this assignment is not used.
*/
if (
scopeStack.scope !==
getCodePathStartScope(variable.scope)
) {
continue;
}
// Variables marked by `markVariableAsUsed()` or
// exported by "exported" block comment.
if (variable.eslintUsed) {
continue;
}
// Variables exported by ESM export syntax
if (variable.scope.type === "module") {
if (
variable.defs.some(
def =>
(def.type === "Variable" &&
def.parent.parent.type ===
"ExportNamedDeclaration") ||
(def.type === "FunctionName" &&
(def.node.parent.type ===
"ExportNamedDeclaration" ||
def.node.parent.type ===
"ExportDefaultDeclaration")) ||
(def.type === "ClassName" &&
(def.node.parent.type ===
"ExportNamedDeclaration" ||
def.node.parent.type ===
"ExportDefaultDeclaration")),
)
) {
continue;
}
if (
variable.references.some(
reference =>
reference.identifier.parent.type ===
"ExportSpecifier",
)
) {
// It have `export { ... }` reference.
continue;
}
}
let list = assignments.get(variable);
if (!list) {
list = [];
assignments.set(variable, list);
}
list.push({
variable,
identifier,
node,
expression,
segments: [...scopeStack.currentSegments],
});
}
},
};
},
};

View File

@@ -0,0 +1,179 @@
# safe-stable-stringify
Safe, deterministic and fast serialization alternative to [JSON.stringify][].
Zero dependencies. ESM and CJS. 100% coverage.
Gracefully handles circular structures and bigint instead of throwing.
Optional custom circular values, deterministic behavior or strict JSON
compatibility check.
## stringify(value[, replacer[, space]])
The same as [JSON.stringify][].
* `value` {any}
* `replacer` {string[]|function|null}
* `space` {number|string}
* Returns: {string}
```js
const stringify = require('safe-stable-stringify')
const bigint = { a: 0, c: 2n, b: 1 }
stringify(bigint)
// '{"a":0,"b":1,"c":2}'
JSON.stringify(bigint)
// TypeError: Do not know how to serialize a BigInt
const circular = { b: 1, a: 0 }
circular.circular = circular
stringify(circular)
// '{"a":0,"b":1,"circular":"[Circular]"}'
JSON.stringify(circular)
// TypeError: Converting circular structure to JSON
stringify(circular, ['a', 'b'], 2)
// {
// "a": 0,
// "b": 1
// }
```
## stringify.configure(options)
* `bigint` {boolean} If `true`, bigint values are converted to a number. Otherwise
they are ignored. **Default:** `true`.
* `circularValue` {string|null|undefined|ErrorConstructor} Defines the value for
circular references. Set to `undefined`, circular properties are not
serialized (array entries are replaced with `null`). Set to `Error`, to throw
on circular references. **Default:** `'[Circular]'`.
* `deterministic` {boolean|function} If `true` or a `Array#sort(comparator)`
comparator method, guarantee a deterministic key order instead of relying on
the insertion order. **Default:** `true`.
* `maximumBreadth` {number} Maximum number of entries to serialize per object
(at least one). The serialized output contains information about how many
entries have not been serialized. Ignored properties are counted as well
(e.g., properties with symbol values). Using the array replacer overrules this
option. **Default:** `Infinity`
* `maximumDepth` {number} Maximum number of object nesting levels (at least 1)
that will be serialized. Objects at the maximum level are serialized as
`'[Object]'` and arrays as `'[Array]'`. **Default:** `Infinity`
* `strict` {boolean} Instead of handling any JSON value gracefully, throw an
error in case it may not be represented as JSON (functions, NaN, ...).
Circular values and bigint values throw as well in case either option is not
explicitly defined. Sets and Maps are not detected as well as Symbol keys!
**Default:** `false`
* Returns: {function} A stringify function with the options applied.
```js
import { configure } from 'safe-stable-stringify'
const stringify = configure({
bigint: true,
circularValue: 'Magic circle!',
deterministic: false,
maximumDepth: 1,
maximumBreadth: 4
})
const circular = {
bigint: 999_999_999_999_999_999n,
typed: new Uint8Array(3),
deterministic: "I don't think so",
}
circular.circular = circular
circular.ignored = true
circular.alsoIgnored = 'Yes!'
const stringified = stringify(circular, null, 4)
console.log(stringified)
// {
// "bigint": 999999999999999999,
// "typed": "[Object]",
// "deterministic": "I don't think so",
// "circular": "Magic circle!",
// "...": "2 items not stringified"
// }
const throwOnCircular = configure({
circularValue: Error
})
throwOnCircular(circular);
// TypeError: Converting circular structure to JSON
```
## Differences to JSON.stringify
1. _Circular values_ are replaced with the string `[Circular]` (configurable).
1. _Object keys_ are sorted instead of using the insertion order (configurable).
1. _BigInt_ values are stringified as regular number instead of throwing a
TypeError (configurable).
1. _Boxed primitives_ (e.g., `Number(5)`) are not unboxed and are handled as
regular object.
Those are the only differences to `JSON.stringify()`. This is a side effect free
variant and [`toJSON`][], [`replacer`][] and the [`spacer`][] work the same as
with `JSON.stringify()`.
## Performance / Benchmarks
Currently this is by far the fastest known stable (deterministic) stringify
implementation. This is especially important for big objects and TypedArrays.
(Dell Precision 5540, i7-9850H CPU @ 2.60GHz, Node.js 16.11.1)
```md
simple: simple object x 3,463,894 ops/sec ±0.44% (98 runs sampled)
simple: circular x 1,236,007 ops/sec ±0.46% (99 runs sampled)
simple: deep x 18,942 ops/sec ±0.41% (93 runs sampled)
simple: deep circular x 18,690 ops/sec ±0.72% (96 runs sampled)
replacer: simple object x 2,664,940 ops/sec ±0.31% (98 runs sampled)
replacer: circular x 1,015,981 ops/sec ±0.09% (99 runs sampled)
replacer: deep x 17,328 ops/sec ±0.38% (97 runs sampled)
replacer: deep circular x 17,071 ops/sec ±0.21% (98 runs sampled)
array: simple object x 3,869,608 ops/sec ±0.22% (98 runs sampled)
array: circular x 3,853,943 ops/sec ±0.45% (96 runs sampled)
array: deep x 3,563,227 ops/sec ±0.20% (100 runs sampled)
array: deep circular x 3,286,475 ops/sec ±0.07% (100 runs sampled)
indentation: simple object x 2,183,162 ops/sec ±0.66% (97 runs sampled)
indentation: circular x 872,538 ops/sec ±0.57% (98 runs sampled)
indentation: deep x 16,795 ops/sec ±0.48% (93 runs sampled)
indentation: deep circular x 16,443 ops/sec ±0.40% (97 runs sampled)
```
Comparing `safe-stable-stringify` with known alternatives:
```md
fast-json-stable-stringify x 18,765 ops/sec ±0.71% (94 runs sampled)
json-stable-stringify x 13,870 ops/sec ±0.72% (94 runs sampled)
fast-stable-stringify x 21,343 ops/sec ±0.33% (95 runs sampled)
faster-stable-stringify x 17,707 ops/sec ±0.44% (97 runs sampled)
json-stringify-deterministic x 11,208 ops/sec ±0.57% (98 runs sampled)
fast-safe-stringify x 21,460 ops/sec ±0.75% (99 runs sampled)
this x 30,367 ops/sec ±0.39% (96 runs sampled)
The fastest is this
```
The `fast-safe-stringify` comparison uses the modules stable implementation.
## Acknowledgements
Sponsored by [MaibornWolff](https://www.maibornwolff.de/) and [nearForm](http://nearform.com)
## License
MIT
[`replacer`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#The%20replacer%20parameter
[`spacer`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#The%20space%20argument
[`toJSON`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#toJSON()_behavior
[JSON.stringify]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify