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,29 @@
import { expect, test } from "vitest";
import * as z from "zod/v4";
test("Georgian locale uses 'ველი' instead of 'სტრინგი'", () => {
// Save original error map to restore later if needed, though tests are usually isolated or we can reset
// const originalErrorMap = z.getErrorMap(); // z.getErrorMap might not exist, but let's assume isolation or just set it
z.setErrorMap(z.locales.ka().localeError);
// Test 1: Invalid type (Expected string, received number)
const stringSchema = z.string();
const numberResult = stringSchema.safeParse(123);
expect(numberResult.success).toBe(false);
if (!numberResult.success) {
// Expected: "არასწორი შეყვანა: მოსალოდნელი ველი, მიღებული რიცხვი"
expect(numberResult.error.issues[0].message).toBe("არასწორი შეყვანა: მოსალოდნელი ველი, მიღებული რიცხვი");
}
// Test 2: Invalid base64
const base64Schema = z.string().base64();
const base64Result = base64Schema.safeParse("not base64!");
expect(base64Result.success).toBe(false);
if (!base64Result.success) {
// Expected: "არასწორი base64-კოდირებული ველი"
// "არასწორი ${FormatDictionary[_issue.format] ?? issue.format}"
// FormatDictionary['base64'] is "base64-კოდირებული ველი"
expect(base64Result.error.issues[0].message).toBe("არასწორი base64-კოდირებული ველი");
}
});

View File

@@ -0,0 +1,51 @@
var _a;
export const $output = Symbol("ZodOutput");
export const $input = Symbol("ZodInput");
export class $ZodRegistry {
constructor() {
this._map = new WeakMap();
this._idmap = new Map();
}
add(schema, ..._meta) {
const meta = _meta[0];
this._map.set(schema, meta);
if (meta && typeof meta === "object" && "id" in meta) {
this._idmap.set(meta.id, schema);
}
return this;
}
clear() {
this._map = new WeakMap();
this._idmap = new Map();
return this;
}
remove(schema) {
const meta = this._map.get(schema);
if (meta && typeof meta === "object" && "id" in meta) {
this._idmap.delete(meta.id);
}
this._map.delete(schema);
return this;
}
get(schema) {
// return this._map.get(schema) as any;
// inherit metadata
const p = schema._zod.parent;
if (p) {
const pm = { ...(this.get(p) ?? {}) };
delete pm.id; // do not inherit id
const f = { ...pm, ...this._map.get(schema) };
return Object.keys(f).length ? f : undefined;
}
return this._map.get(schema);
}
has(schema) {
return this._map.has(schema);
}
}
// registries
export function registry() {
return new $ZodRegistry();
}
(_a = globalThis).__zod_globalRegistry ?? (_a.__zod_globalRegistry = registry());
export const globalRegistry = globalThis.__zod_globalRegistry;

View File

@@ -0,0 +1 @@
{"version":3,"file":"assertions.d.ts","sourceRoot":"","sources":["../../src/assertions.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,6BAA6B,CACzC,gBAAgB,EAAE,MAAM,EACxB,GAAG,EAAE,MAAM,GAAG,MAAM,EACpB,GAAG,EAAE,MAAM,GAAG,MAAM,EACpB,KAAK,EAAE,MAAM,GAAG,MAAM,QAUzB"}

View File

@@ -0,0 +1,427 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("@typescript-eslint/utils");
const tsutils = __importStar(require("ts-api-utils"));
const ts = __importStar(require("typescript"));
const util_1 = require("../util");
exports.default = (0, util_1.createRule)({
name: 'no-unnecessary-type-parameters',
meta: {
type: 'problem',
docs: {
description: "Disallow type parameters that aren't used multiple times",
recommended: 'strict',
requiresTypeChecking: true,
},
hasSuggestions: true,
messages: {
replaceUsagesWithConstraint: 'Replace all usages of type parameter with its constraint.',
sole: 'Type parameter {{name}} is {{uses}} in the {{descriptor}} signature.',
},
schema: [],
},
defaultOptions: [],
create(context) {
const parserServices = (0, util_1.getParserServices)(context);
function checkNode(node, descriptor) {
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
const checker = parserServices.program.getTypeChecker();
let counts;
// Get the scope in which the type parameters are declared.
const scope = context.sourceCode.getScope(node);
for (const typeParameter of tsNode.typeParameters) {
const esTypeParameter = parserServices.tsNodeToESTreeNodeMap.get(typeParameter);
const smTypeParameterVariable = (0, util_1.nullThrows)((() => {
const variable = scope.set.get(esTypeParameter.name.name);
return variable?.isTypeVariable ? variable : undefined;
})(), "Type parameter should be present in scope's variables.");
// Quick path: if the type parameter is used multiple times in the AST,
// we don't need to dip into types to know it's repeated.
if (isTypeParameterRepeatedInAST(esTypeParameter, smTypeParameterVariable.references, node.body?.range[0] ?? node.returnType?.range[1])) {
continue;
}
// For any inferred types, we have to dip into type checking.
counts ??= countTypeParameterUsage(checker, tsNode);
const identifierCounts = counts.get(typeParameter.name);
if (!identifierCounts || identifierCounts > 2) {
continue;
}
context.report({
node: esTypeParameter,
messageId: 'sole',
data: {
name: typeParameter.name.text,
descriptor,
uses: identifierCounts === 1 ? 'never used' : 'used only once',
},
suggest: [
{
messageId: 'replaceUsagesWithConstraint',
*fix(fixer) {
// Replace all the usages of the type parameter with the constraint...
const constraint = esTypeParameter.constraint;
// special case - a constraint of 'any' actually acts like 'unknown'
const constraintText = constraint != null &&
constraint.type !== utils_1.AST_NODE_TYPES.TSAnyKeyword
? context.sourceCode.getText(constraint)
: 'unknown';
for (const reference of smTypeParameterVariable.references) {
if (reference.isTypeReference) {
const referenceNode = reference.identifier;
const isComplexType = constraint?.type === utils_1.AST_NODE_TYPES.TSUnionType ||
constraint?.type === utils_1.AST_NODE_TYPES.TSIntersectionType ||
constraint?.type === utils_1.AST_NODE_TYPES.TSConditionalType;
const hasMatchingAncestorType = [
utils_1.AST_NODE_TYPES.TSArrayType,
utils_1.AST_NODE_TYPES.TSIndexedAccessType,
utils_1.AST_NODE_TYPES.TSIntersectionType,
utils_1.AST_NODE_TYPES.TSUnionType,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
].some(type => referenceNode.parent.parent.type === type);
if (isComplexType && hasMatchingAncestorType) {
const fixResult = (0, util_1.getWrappingFixer)({
node: referenceNode,
innerNode: constraint,
sourceCode: context.sourceCode,
wrap: constraintNode => constraintNode,
})(fixer);
yield fixResult;
}
else {
yield fixer.replaceText(referenceNode, constraintText);
}
}
}
// ...and remove the type parameter itself from the declaration.
const typeParamsNode = (0, util_1.nullThrows)(node.typeParameters, 'node should have type parameters');
// We are assuming at this point that the reported type parameter
// is present in the inspected node's type parameters.
if (typeParamsNode.params.length === 1) {
// Remove the whole <T> generic syntax if we're removing the only type parameter in the list.
yield fixer.remove(typeParamsNode);
}
else {
const index = typeParamsNode.params.indexOf(esTypeParameter);
if (index === 0) {
const commaAfter = (0, util_1.nullThrows)(context.sourceCode.getTokenAfter(esTypeParameter, token => token.value === ','), util_1.NullThrowsReasons.MissingToken('comma', 'type parameter list'));
const tokenAfterComma = (0, util_1.nullThrows)(context.sourceCode.getTokenAfter(commaAfter, {
includeComments: true,
}), util_1.NullThrowsReasons.MissingToken('token', 'type parameter list'));
yield fixer.removeRange([
esTypeParameter.range[0],
tokenAfterComma.range[0],
]);
}
else {
const commaBefore = (0, util_1.nullThrows)(context.sourceCode.getTokenBefore(esTypeParameter, token => token.value === ','), util_1.NullThrowsReasons.MissingToken('comma', 'type parameter list'));
yield fixer.removeRange([
commaBefore.range[0],
esTypeParameter.range[1],
]);
}
}
},
},
],
});
}
}
return {
[[
'ArrowFunctionExpression[typeParameters]',
'FunctionDeclaration[typeParameters]',
'FunctionExpression[typeParameters]',
'TSCallSignatureDeclaration[typeParameters]',
'TSConstructorType[typeParameters]',
'TSDeclareFunction[typeParameters]',
'TSEmptyBodyFunctionExpression[typeParameters]',
'TSFunctionType[typeParameters]',
'TSMethodSignature[typeParameters]',
].join(', ')](node) {
checkNode(node, 'function');
},
[[
'ClassDeclaration[typeParameters]',
'ClassExpression[typeParameters]',
].join(', ')](node) {
checkNode(node, 'class');
},
};
},
});
function isTypeParameterRepeatedInAST(node, references, startOfBody = Infinity) {
let total = 0;
for (const reference of references) {
// References inside the type parameter's definition don't count...
if (reference.identifier.range[0] < node.range[1] &&
reference.identifier.range[1] > node.range[0]) {
continue;
}
// ...nor references that are outside the declaring signature.
if (reference.identifier.range[0] > startOfBody) {
continue;
}
// Neither do references that aren't to the same type parameter,
// namely value-land (non-type) identifiers of the type parameter's type,
// and references to different type parameters or values.
if (!reference.isTypeReference ||
reference.identifier.name !== node.name.name) {
continue;
}
// If the type parameter is being used as a type argument, then we
// know the type parameter is being reused and can't be reported.
if (reference.identifier.parent.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
const grandparent = skipConstituentsUpward(reference.identifier.parent.parent);
if (grandparent.type === utils_1.AST_NODE_TYPES.TSTypeParameterInstantiation &&
grandparent.params.includes(reference.identifier.parent) &&
// Array and ReadonlyArray must be handled carefully
// let's defer the check to the type-aware phase
!(grandparent.parent.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
grandparent.parent.typeName.type === utils_1.AST_NODE_TYPES.Identifier &&
['Array', 'ReadonlyArray'].includes(grandparent.parent.typeName.name))) {
return true;
}
}
total += 1;
if (total >= 2) {
return true;
}
}
return false;
}
function skipConstituentsUpward(node) {
switch (node.type) {
case utils_1.AST_NODE_TYPES.TSIntersectionType:
case utils_1.AST_NODE_TYPES.TSUnionType:
return skipConstituentsUpward(node.parent);
default:
return node;
}
}
/**
* Count uses of type parameters in inferred return types.
* We need to resolve and analyze the inferred return type of a function
* to see whether it contains additional references to the type parameters.
* For classes, we need to do this for all their methods.
*/
function countTypeParameterUsage(checker, node) {
const counts = new Map();
if (ts.isClassLike(node)) {
for (const typeParameter of node.typeParameters) {
collectTypeParameterUsageCounts(checker, typeParameter, counts, true);
}
for (const member of node.members) {
collectTypeParameterUsageCounts(checker, member, counts, true);
}
}
else {
collectTypeParameterUsageCounts(checker, node, counts, false);
}
return counts;
}
/**
* Populates {@link foundIdentifierUsages} by the number of times each type parameter
* appears in the given type by checking its uses through its type references.
* This is essentially a limited subset of the scope manager, but for types.
*/
function collectTypeParameterUsageCounts(checker, node, foundIdentifierUsages, fromClass) {
const visitedSymbolLists = new Set();
const type = checker.getTypeAtLocation(node);
const typeUsages = new Map();
const visitedConstraints = new Set();
let functionLikeType = false;
let visitedDefault = false;
if (ts.isCallSignatureDeclaration(node) ||
ts.isConstructorDeclaration(node)) {
functionLikeType = true;
visitSignature(checker.getSignatureFromDeclaration(node));
}
if (!functionLikeType) {
visitType(type, false);
}
function visitType(type, assumeMultipleUses, isReturnType = false) {
// Seeing the same type > (threshold=3 ** 2) times indicates a likely
// recursive type, like `type T = { [P in keyof T]: T }`.
// If it's not recursive, then heck, we've seen it enough times that any
// referenced types have been counted enough to qualify as used.
if (!type || incrementTypeUsages(type) > 9) {
return;
}
if (tsutils.isTypeParameter(type)) {
const declaration = type.getSymbol()?.getDeclarations()?.[0];
if (declaration) {
incrementIdentifierCount(declaration.name, assumeMultipleUses);
// Visiting the type of a constrained type parameter will recurse into
// the constraint. We avoid infinite loops by visiting each only once.
if (declaration.constraint &&
!visitedConstraints.has(declaration.constraint)) {
visitedConstraints.add(declaration.constraint);
visitType(checker.getTypeAtLocation(declaration.constraint), false);
}
if (declaration.default && !visitedDefault) {
visitedDefault = true;
visitType(checker.getTypeAtLocation(declaration.default), false);
}
}
}
// Catch-all: generic type references like `Exclude<T, null>`
else if (type.aliasTypeArguments) {
// We don't descend into the definition of the type alias, so we don't
// know whether it's used multiple times. It's safest to assume it is.
visitTypesList(type.aliasTypeArguments, true);
}
// Intersections and unions like `0 | 1`
else if (tsutils.isUnionOrIntersectionType(type)) {
visitTypesList(type.types, assumeMultipleUses);
}
// Index access types like `T[K]`
else if (tsutils.isIndexedAccessType(type)) {
visitType(type.objectType, assumeMultipleUses);
visitType(type.indexType, assumeMultipleUses);
}
// Tuple types like `[K, V]`
// Generic type references like `Map<K, V>`
else if (tsutils.isTypeReference(type)) {
for (const typeArgument of type.typeArguments ?? []) {
// currently, if we are in a "class context", everything is accepted
let thisAssumeMultipleUses = fromClass || assumeMultipleUses;
// special cases - readonly arrays/tuples are considered only to use the
// type parameter once. Mutable arrays/tuples are considered to use the
// type parameter multiple times if and only if they are returned.
// other kind of type references always count as multiple uses
thisAssumeMultipleUses ||= tsutils.isTupleType(type.target)
? isReturnType && !type.target.readonly
: checker.isArrayType(type.target)
? isReturnType &&
type.symbol?.getName() === 'Array'
: true;
visitType(typeArgument, thisAssumeMultipleUses, isReturnType);
}
}
// Template literals like `a${T}b`
else if (tsutils.isTemplateLiteralType(type)) {
for (const subType of type.types) {
visitType(subType, assumeMultipleUses);
}
}
// Conditional types like `T extends string ? T : never`
else if (tsutils.isConditionalType(type)) {
visitType(type.checkType, assumeMultipleUses);
visitType(type.extendsType, assumeMultipleUses);
}
// Catch-all: inferred object types like `{ K: V }`.
// These catch-alls should be _after_ more specific checks like
// `isTypeReference` to avoid descending into all the properties of a
// generic interface/class, e.g. `Map<K, V>`.
else if (tsutils.isObjectType(type)) {
const properties = type.getProperties();
visitSymbolsListOnce(properties, false);
if (isMappedType(type)) {
visitType(type.typeParameter, false);
if (properties.length === 0) {
// TS treats mapped types like `{[k in "a"]: T}` like `{a: T}`.
// They have properties, so we need to avoid double-counting.
visitType(type.templateType ?? type.constraintType, false);
}
// TS doesn't count mapped types key remapping (`{[K in 'a' as T]: K}`)
// but handles this under `MappedType.nameType`, so we need to visit that too.
if (type.nameType) {
visitType(type.nameType, false);
}
}
visitType(type.getNumberIndexType(), true);
visitType(type.getStringIndexType(), true);
type.getCallSignatures().forEach(signature => {
functionLikeType = true;
visitSignature(signature);
});
type.getConstructSignatures().forEach(signature => {
functionLikeType = true;
visitSignature(signature);
});
}
// Catch-all: operator types like `keyof T`
else if (isOperatorType(type)) {
visitType(type.type, assumeMultipleUses);
}
}
function incrementIdentifierCount(id, assumeMultipleUses) {
const identifierCount = foundIdentifierUsages.get(id) ?? 0;
const value = assumeMultipleUses ? 2 : 1;
foundIdentifierUsages.set(id, identifierCount + value);
}
function incrementTypeUsages(type) {
const count = (typeUsages.get(type) ?? 0) + 1;
typeUsages.set(type, count);
return count;
}
function visitSignature(signature) {
if (!signature) {
return;
}
if (signature.thisParameter) {
visitType(checker.getTypeOfSymbol(signature.thisParameter), false);
}
for (const parameter of signature.parameters) {
visitType(checker.getTypeOfSymbol(parameter), false);
}
for (const typeParameter of signature.getTypeParameters() ?? []) {
visitType(typeParameter, false);
}
visitType(checker.getTypePredicateOfSignature(signature)?.type ??
signature.getReturnType(), false, true);
}
function visitSymbolsListOnce(symbols, assumeMultipleUses) {
if (visitedSymbolLists.has(symbols)) {
return;
}
visitedSymbolLists.add(symbols);
for (const symbol of symbols) {
visitType(checker.getTypeOfSymbol(symbol), assumeMultipleUses);
}
}
function visitTypesList(types, assumeMultipleUses) {
for (const type of types) {
visitType(type, assumeMultipleUses);
}
}
}
function isMappedType(type) {
return 'typeParameter' in type;
}
function isOperatorType(type) {
return 'type' in type && !!type.type;
}

View File

@@ -0,0 +1,52 @@
# utf-8-validate
[![Version npm](https://img.shields.io/npm/v/utf-8-validate.svg?logo=npm)](https://www.npmjs.com/package/utf-8-validate)
[![Linux/macOS/Windows Build](https://img.shields.io/github/actions/workflow/status/websockets/utf-8-validate/ci.yml?branch=master&label=build&logo=github)](https://github.com/websockets/utf-8-validate/actions?query=workflow%3ACI+branch%3Amaster)
Check if a buffer contains valid UTF-8 encoded text.
## Installation
```
npm install utf-8-validate --save-optional
```
The `--save-optional` flag tells npm to save the package in your package.json
under the
[`optionalDependencies`](https://docs.npmjs.com/files/package.json#optionaldependencies)
key.
## API
The module exports a single function that takes one argument. To maximize
performance, the argument is not validated. It is the caller's responsibility to
ensure that it is correct.
### `isValidUTF8(buffer)`
Checks whether a buffer contains valid UTF-8.
#### Arguments
- `buffer` - The buffer to check.
#### Return value
`true` if the buffer contains only correct UTF-8, else `false`.
#### Example
```js
'use strict';
const isValidUTF8 = require('utf-8-validate');
const buf = Buffer.from([0xf0, 0x90, 0x80, 0x80]);
console.log(isValidUTF8(buf));
// => true
```
## License
[MIT](LICENSE)

View File

@@ -0,0 +1,35 @@
{
"JSON.stringify@native": {
"name": "JSON.stringify@native",
"browser": "IE 11.0.0 (Windows 7 0.0.0)",
"suite": "libs",
"hz": 14003.509566928884,
"success": true,
"fastest": false,
"rme": 0.014594535695529036,
"rhz": 2.187283150730621,
"sampleSize": 174
},
"fast-stable-stringify@a9f81e8": {
"name": "fast-stable-stringify@a9f81e8",
"browser": "IE 11.0.0 (Windows 7 0.0.0)",
"suite": "libs",
"hz": 6402.239034416405,
"success": true,
"fastest": true,
"rme": 0.013900958827880387,
"rhz": 1,
"sampleSize": 150
},
"json-stable-stringify@1.0.1": {
"name": "json-stable-stringify@1.0.1",
"browser": "IE 11.0.0 (Windows 7 0.0.0)",
"suite": "libs",
"hz": 4699.054054054045,
"success": true,
"fastest": false,
"rme": 0.01732984212468934,
"rhz": 0.7339704170358873,
"sampleSize": 173
}
}

View File

@@ -0,0 +1,9 @@
import * as ts from 'typescript';
import type { TSESTree } from './ts-estree';
/**
* Convert all comments for the given AST.
* @param ast the AST object
* @returns the converted ESTreeComment
* @private
*/
export declare function convertComments(ast: ts.SourceFile): TSESTree.Comment[];

View File

@@ -0,0 +1,22 @@
var _typeof = require("./typeof.js")["default"];
function _interopRequireWildcard(e, t) {
if ("function" == typeof WeakMap) var r = new WeakMap(),
n = new WeakMap();
return (module.exports = _interopRequireWildcard = function _interopRequireWildcard(e, t) {
if (!t && e && e.__esModule) return e;
var o,
i,
f = {
__proto__: null,
"default": e
};
if (null === e || "object" != _typeof(e) && "function" != typeof e) return f;
if (o = t ? n : r) {
if (o.has(e)) return o.get(e);
o.set(e, f);
}
for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]);
return f;
}, module.exports.__esModule = true, module.exports["default"] = module.exports)(e, t);
}
module.exports = _interopRequireWildcard, module.exports.__esModule = true, module.exports["default"] = module.exports;

View File

@@ -0,0 +1,4 @@
{
"main": "../../cjs/_array_without_holes.cjs",
"module": "../../esm/_array_without_holes.js"
}

View File

@@ -0,0 +1,41 @@
import { isBuiltin } from 'node:module';
let port;
const initialize = async ({ port: _port, time: _time }) => {
port = _port;
};
const NOW_LENGTH = Date.now().toString().length;
const REGEXP_VITEST = new RegExp(`%3Fvitest=\\d{${NOW_LENGTH}}`);
const REGEXP_MOCK_ACTUAL = /\?mock=actual/;
const resolve = (specifier, context, defaultResolve) => {
if (specifier.includes("mock=actual")) {
// url is already resolved by `importActual`
const moduleId = specifier.replace(REGEXP_MOCK_ACTUAL, "");
return {
url: moduleId,
format: isBuiltin(moduleId) ? "builtin" : void 0,
shortCircuit: true
};
}
const isVitest = specifier.includes("%3Fvitest=");
const result = defaultResolve(isVitest ? specifier.replace(REGEXP_VITEST, "") : specifier, context);
if (!port || !context?.parentURL) return result;
if (typeof result === "object" && "then" in result) return result.then((resolved) => {
ensureModuleGraphEntry(resolved.url, context.parentURL);
if (isVitest) resolved.url = `${resolved.url}?vitest=${Date.now()}`;
return resolved;
});
if (isVitest) result.url = `${result.url}?vitest=${Date.now()}`;
ensureModuleGraphEntry(result.url, context.parentURL);
return result;
};
function ensureModuleGraphEntry(url, parentURL) {
if (url.includes("/node_modules/")) return;
port.postMessage({
event: "register-module-graph-entry",
url,
parentURL
});
}
export { initialize, resolve };

View File

@@ -0,0 +1 @@
{"version":3,"file":"ast.d.ts","sourceRoot":"","sources":["../../src/ast.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAwC5D,MAAM,MAAM,WAAW,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAA;AAgJrD,qBAAa,GAAG;;IACd,IAAI,EAAE,WAAW,GAAG,IAAI,CAAA;IAexB,EAAE,SAAO;IAET,IAAI,KAAK,IAAI,MAAM,CAElB;gBAgBC,IAAI,EAAE,WAAW,GAAG,IAAI,EACxB,MAAM,CAAC,EAAE,GAAG,EACZ,OAAO,GAAE,gBAAqB;IAahC,IAAI,QAAQ,IAAI,OAAO,GAAG,SAAS,CAUlC;IAGD,QAAQ,IAAI,MAAM;IAkDlB,IAAI,CAAC,GAAG,KAAK,EAAE,CAAC,MAAM,GAAG,GAAG,CAAC,EAAE;IAe/B,MAAM;IAkBN,OAAO,IAAI,OAAO;IAgBlB,KAAK,IAAI,OAAO;IAYhB,MAAM,CAAC,IAAI,EAAE,GAAG,GAAG,MAAM;IAKzB,KAAK,CAAC,MAAM,EAAE,GAAG;IAsQjB,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB;IAQ/D,WAAW,IAAI,QAAQ,GAAG,MAAM;IA2BhC,IAAI,OAAO,qBAEV;IAuED,cAAc,CACZ,QAAQ,CAAC,EAAE,OAAO,GACjB,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC;CA6OjE"}

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Tinylibs
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,649 @@
'use strict'
const { test } = require('tape')
const j = require('..')
test('parse', t => {
t.test('parses object string', t => {
t.deepEqual(
j.parse('{"a": 5, "b": 6}'),
JSON.parse('{"a": 5, "b": 6}')
)
t.end()
})
t.test('parses null string', t => {
t.strictEqual(
j.parse('null'),
JSON.parse('null')
)
t.end()
})
t.test('parses 0 string', t => {
t.strictEqual(
j.parse('0'),
JSON.parse('0')
)
t.end()
})
t.test('parses string string', t => {
t.strictEqual(
j.parse('"X"'),
JSON.parse('"X"')
)
t.end()
})
t.test('parses buffer', t => {
t.strictEqual(
j.parse(Buffer.from('"X"')),
JSON.parse(Buffer.from('"X"'))
)
t.end()
})
t.test('parses object string (reviver)', t => {
const reviver = (_key, value) => {
return typeof value === 'number' ? value + 1 : value
}
t.deepEqual(
j.parse('{"a": 5, "b": 6}', reviver),
JSON.parse('{"a": 5, "b": 6}', reviver)
)
t.end()
})
t.test('protoAction', t => {
t.test('sanitizes object string (reviver, options)', t => {
const reviver = (_key, value) => {
return typeof value === 'number' ? value + 1 : value
}
t.deepEqual(
j.parse('{"a": 5, "b": 6,"__proto__": { "x": 7 }}', reviver, { protoAction: 'remove' }),
{ a: 6, b: 7 }
)
t.end()
})
t.test('sanitizes object string (options)', t => {
t.deepEqual(
j.parse('{"a": 5, "b": 6,"__proto__": { "x": 7 }}', { protoAction: 'remove' }),
{ a: 5, b: 6 }
)
t.end()
})
t.test('sanitizes object string (null, options)', t => {
t.deepEqual(
j.parse('{"a": 5, "b": 6,"__proto__": { "x": 7 }}', null, { protoAction: 'remove' }),
{ a: 5, b: 6 }
)
t.end()
})
t.test('sanitizes object string (null, options)', t => {
t.deepEqual(
j.parse('{"a": 5, "b": 6,"__proto__": { "x": 7 }}', { protoAction: 'remove' }),
{ a: 5, b: 6 }
)
t.end()
})
t.test('sanitizes nested object string', t => {
t.deepEqual(
j.parse('{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }', { protoAction: 'remove' }),
{ a: 5, b: 6, c: { d: 0, e: 'text', f: { g: 2 } } }
)
t.end()
})
t.test('ignores proto property', t => {
t.deepEqual(
j.parse('{ "a": 5, "b": 6, "__proto__": { "x": 7 } }', { protoAction: 'ignore' }),
JSON.parse('{ "a": 5, "b": 6, "__proto__": { "x": 7 } }')
)
t.end()
})
t.test('ignores proto value', t => {
t.deepEqual(
j.parse('{"a": 5, "b": "__proto__"}'),
{ a: 5, b: '__proto__' }
)
t.end()
})
t.test('errors on proto property', t => {
t.throws(() => j.parse('{ "a": 5, "b": 6, "__proto__": { "x": 7 } }'), SyntaxError)
t.throws(() => j.parse('{ "a": 5, "b": 6, "__proto__" : { "x": 7 } }'), SyntaxError)
t.throws(() => j.parse('{ "a": 5, "b": 6, "__proto__" \n\r\t : { "x": 7 } }'), SyntaxError)
t.throws(() => j.parse('{ "a": 5, "b": 6, "__proto__" \n \r \t : { "x": 7 } }'), SyntaxError)
t.end()
})
t.test('errors on proto property (null, null)', t => {
t.throws(() => j.parse('{ "a": 5, "b": 6, "__proto__": { "x": 7 } }', null, null), SyntaxError)
t.end()
})
t.test('errors on proto property (explicit options)', t => {
t.throws(() => j.parse('{ "a": 5, "b": 6, "__proto__": { "x": 7 } }', { protoAction: 'error' }), SyntaxError)
t.end()
})
t.test('errors on proto property (unicode)', t => {
t.throws(() => j.parse('{ "a": 5, "b": 6, "\\u005f_proto__": { "x": 7 } }'), SyntaxError)
t.throws(() => j.parse('{ "a": 5, "b": 6, "_\\u005fp\\u0072oto__": { "x": 7 } }'), SyntaxError)
t.throws(() => j.parse('{ "a": 5, "b": 6, "\\u005f\\u005f\\u0070\\u0072\\u006f\\u0074\\u006f\\u005f\\u005f": { "x": 7 } }'), SyntaxError)
t.throws(() => j.parse('{ "a": 5, "b": 6, "\\u005F_proto__": { "x": 7 } }'), SyntaxError)
t.throws(() => j.parse('{ "a": 5, "b": 6, "_\\u005Fp\\u0072oto__": { "x": 7 } }'), SyntaxError)
t.throws(() => j.parse('{ "a": 5, "b": 6, "\\u005F\\u005F\\u0070\\u0072\\u006F\\u0074\\u006F\\u005F\\u005F": { "x": 7 } }'), SyntaxError)
t.end()
})
t.test('should reset stackTraceLimit', t => {
const text = '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }'
Error.stackTraceLimit = 42
t.throws(() => j.parse(text))
t.same(Error.stackTraceLimit, 42)
t.end()
})
t.end()
})
t.test('constructorAction', t => {
t.test('sanitizes object string (reviver, options)', t => {
const reviver = (_key, value) => {
return typeof value === 'number' ? value + 1 : value
}
t.deepEqual(
j.parse('{"a": 5, "b": 6, "constructor":{"prototype":{"bar":"baz"}} }', reviver, { constructorAction: 'remove' }),
{ a: 6, b: 7 }
)
t.end()
})
t.test('sanitizes object string (options)', t => {
t.deepEqual(
j.parse('{"a": 5, "b": 6, "constructor":{"prototype":{"bar":"baz"}} }', { constructorAction: 'remove' }),
{ a: 5, b: 6 }
)
t.end()
})
t.test('sanitizes object string (null, options)', t => {
t.deepEqual(
j.parse('{"a": 5, "b": 6,"constructor":{"prototype":{"bar":"baz"}} }', null, { constructorAction: 'remove' }),
{ a: 5, b: 6 }
)
t.end()
})
t.test('sanitizes object string (null, options)', t => {
t.deepEqual(
j.parse('{"a": 5, "b": 6,"constructor":{"prototype":{"bar":"baz"}} }', { constructorAction: 'remove' }),
{ a: 5, b: 6 }
)
t.end()
})
t.test('sanitizes object string (no prototype key)', t => {
t.deepEqual(
j.parse('{"a": 5, "b": 6,"constructor":{"bar":"baz"} }', { constructorAction: 'remove' }),
{ a: 5, b: 6, constructor: { bar: 'baz' } }
)
t.end()
})
t.test('sanitizes nested object string', t => {
t.deepEqual(
j.parse('{ "a": 5, "b": 6, "constructor":{"prototype":{"bar":"baz"}}, "c": { "d": 0, "e": "text", "constructor":{"prototype":{"bar":"baz"}}, "f": { "g": 2 } } }', { constructorAction: 'remove' }),
{ a: 5, b: 6, c: { d: 0, e: 'text', f: { g: 2 } } }
)
t.end()
})
t.test('ignores proto property', t => {
t.deepEqual(
j.parse('{ "a": 5, "b": 6, "constructor":{"prototype":{"bar":"baz"}} }', { constructorAction: 'ignore' }),
JSON.parse('{ "a": 5, "b": 6, "constructor":{"prototype":{"bar":"baz"}} }')
)
t.end()
})
t.test('ignores proto value', t => {
t.deepEqual(
j.parse('{"a": 5, "b": "constructor"}'),
{ a: 5, b: 'constructor' }
)
t.end()
})
t.test('errors on proto property', t => {
t.throws(() => j.parse('{ "a": 5, "b": 6, "constructor": {"prototype":{"bar":"baz"}} }'), SyntaxError)
t.throws(() => j.parse('{ "a": 5, "b": 6, "constructor" : {"prototype":{"bar":"baz"}} }'), SyntaxError)
t.throws(() => j.parse('{ "a": 5, "b": 6, "constructor" \n\r\t : {"prototype":{"bar":"baz"}} }'), SyntaxError)
t.throws(() => j.parse('{ "a": 5, "b": 6, "constructor" \n \r \t : {"prototype":{"bar":"baz"}} }'), SyntaxError)
t.end()
})
t.test('Should not throw if the constructor key hasn\'t a child named prototype', t => {
t.doesNotThrow(() => j.parse('{ "a": 5, "b": 6, "constructor":{"bar":"baz"} }', null, null), SyntaxError)
t.end()
})
t.test('errors on proto property (null, null)', t => {
t.throws(() => j.parse('{ "a": 5, "b": 6, "constructor":{"prototype":{"bar":"baz"}} }', null, null), SyntaxError)
t.end()
})
t.test('errors on proto property (explicit options)', t => {
t.throws(() => j.parse('{ "a": 5, "b": 6, "constructor":{"prototype":{"bar":"baz"}} }', { constructorAction: 'error' }), SyntaxError)
t.end()
})
t.test('errors on proto property (unicode)', t => {
t.throws(() => j.parse('{ "a": 5, "b": 6, "\\u0063\\u006fnstructor": {"prototype":{"bar":"baz"}} }'), SyntaxError)
t.throws(() => j.parse('{ "a": 5, "b": 6, "\\u0063\\u006f\\u006e\\u0073\\u0074ructor": {"prototype":{"bar":"baz"}} }'), SyntaxError)
t.throws(() => j.parse('{ "a": 5, "b": 6, "\\u0063\\u006f\\u006e\\u0073\\u0074\\u0072\\u0075\\u0063\\u0074\\u006f\\u0072": {"prototype":{"bar":"baz"}} }'), SyntaxError)
t.throws(() => j.parse('{ "a": 5, "b": 6, "\\u0063\\u006Fnstructor": {"prototype":{"bar":"baz"}} }'), SyntaxError)
t.throws(() => j.parse('{ "a": 5, "b": 6, "\\u0063\\u006F\\u006E\\u0073\\u0074\\u0072\\u0075\\u0063\\u0074\\u006F\\u0072": {"prototype":{"bar":"baz"}} }'), SyntaxError)
t.end()
})
t.test('handles constructor null safely', t => {
// Test that constructor: null doesn't trigger prototype pollution checks
t.deepEqual(
j.parse('{"constructor": null}', { constructorAction: 'remove' }),
{ constructor: null }
)
// Test that constructor: null doesn't throw error when using error action
t.deepEqual(
j.parse('{"constructor": null}', { constructorAction: 'error' }),
{ constructor: null }
)
// Test that constructor: null is preserved when using ignore action
t.deepEqual(
j.parse('{"constructor": null}', { constructorAction: 'ignore' }),
{ constructor: null }
)
t.end()
})
t.end()
})
t.test('protoAction and constructorAction', t => {
t.test('protoAction=remove constructorAction=remove', t => {
t.deepEqual(
j.parse(
'{"a": 5, "b": 6, "constructor":{"prototype":{"bar":"baz"}}, "__proto__": { "x": 7 } }',
{ protoAction: 'remove', constructorAction: 'remove' }
),
{ a: 5, b: 6 }
)
t.end()
})
t.test('protoAction=ignore constructorAction=remove', t => {
t.deepEqual(
j.parse(
'{"a": 5, "b": 6, "constructor":{"prototype":{"bar":"baz"}}, "__proto__": { "x": 7 } }',
{ protoAction: 'ignore', constructorAction: 'remove' }
),
JSON.parse('{ "a": 5, "b": 6, "__proto__": { "x": 7 } }')
)
t.end()
})
t.test('protoAction=remove constructorAction=ignore', t => {
t.deepEqual(
j.parse(
'{"a": 5, "b": 6, "constructor":{"prototype":{"bar":"baz"}}, "__proto__": { "x": 7 } }',
{ protoAction: 'remove', constructorAction: 'ignore' }
),
JSON.parse('{ "a": 5, "b": 6, "constructor":{"prototype":{"bar":"baz"}} }')
)
t.end()
})
t.test('protoAction=ignore constructorAction=ignore', t => {
t.deepEqual(
j.parse(
'{"a": 5, "b": 6, "constructor":{"prototype":{"bar":"baz"}}, "__proto__": { "x": 7 } }',
{ protoAction: 'ignore', constructorAction: 'ignore' }
),
JSON.parse('{ "a": 5, "b": 6, "constructor":{"prototype":{"bar":"baz"}}, "__proto__": { "x": 7 } }')
)
t.end()
})
t.test('protoAction=error constructorAction=ignore', t => {
t.throws(() => j.parse(
'{"a": 5, "b": 6, "constructor":{"prototype":{"bar":"baz"}}, "__proto__": { "x": 7 } }',
{ protoAction: 'error', constructorAction: 'ignore' }
), SyntaxError)
t.end()
})
t.test('protoAction=ignore constructorAction=error', t => {
t.throws(() => j.parse(
'{"a": 5, "b": 6, "constructor":{"prototype":{"bar":"baz"}}, "__proto__": { "x": 7 } }',
{ protoAction: 'ignore', constructorAction: 'error' }
), SyntaxError)
t.end()
})
t.test('protoAction=error constructorAction=error', t => {
t.throws(() => j.parse(
'{"a": 5, "b": 6, "constructor":{"prototype":{"bar":"baz"}}, "__proto__": { "x": 7 } }',
{ protoAction: 'error', constructorAction: 'error' }
), SyntaxError)
t.end()
})
t.end()
})
t.test('sanitizes nested object string', t => {
const text = '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }'
const obj = j.parse(text, { protoAction: 'remove' })
t.deepEqual(obj, { a: 5, b: 6, c: { d: 0, e: 'text', f: { g: 2 } } })
t.end()
})
t.test('errors on constructor property', t => {
const text = '{ "a": 5, "b": 6, "constructor": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }'
t.throws(() => j.parse(text), SyntaxError)
t.end()
})
t.test('errors on proto property', t => {
const text = '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }'
t.throws(() => j.parse(text), SyntaxError)
t.end()
})
t.test('errors on constructor property', t => {
const text = '{ "a": 5, "b": 6, "constructor": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }'
t.throws(() => j.parse(text), SyntaxError)
t.end()
})
t.test('does not break when hasOwnProperty is overwritten', t => {
const text = '{ "a": 5, "b": 6, "hasOwnProperty": "text", "__proto__": { "x": 7 } }'
const obj = j.parse(text, { protoAction: 'remove' })
t.deepEqual(obj, { a: 5, b: 6, hasOwnProperty: 'text' })
t.end()
})
t.end()
})
test('safeParse', t => {
t.test('parses buffer', t => {
t.strictEqual(
j.safeParse(Buffer.from('"X"')),
JSON.parse(Buffer.from('"X"'))
)
t.end()
})
t.test('should reset stackTraceLimit', t => {
const text = '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }'
Error.stackTraceLimit = 42
t.same(j.safeParse(text), null)
t.same(Error.stackTraceLimit, 42)
t.end()
})
t.test('sanitizes nested object string', t => {
const text = '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }'
t.same(j.safeParse(text), null)
t.end()
})
t.test('returns null on constructor property', t => {
const text = '{ "a": 5, "b": 6, "constructor": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }'
t.same(j.safeParse(text), null)
t.end()
})
t.test('returns null on proto property', t => {
const text = '{ "a": 5, "b": 6, "__proto__": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }'
t.same(j.safeParse(text), null)
t.end()
})
t.test('returns null on constructor property', t => {
const text = '{ "a": 5, "b": 6, "constructor": { "x": 7 }, "c": { "d": 0, "e": "text", "__proto__": { "y": 8 }, "f": { "g": 2 } } }'
t.same(j.safeParse(text), null)
t.end()
})
t.test('parses object string', t => {
t.deepEqual(
j.safeParse('{"a": 5, "b": 6}'),
{ a: 5, b: 6 }
)
t.end()
})
t.test('returns null on proto object string', t => {
t.strictEqual(
j.safeParse('{ "a": 5, "b": 6, "__proto__": { "x": 7 } }'),
null
)
t.end()
})
t.test('returns undefined on invalid object string', t => {
t.strictEqual(
j.safeParse('{"a": 5, "b": 6'),
undefined
)
t.end()
})
t.test('sanitizes object string (options)', t => {
t.deepEqual(
j.safeParse('{"a": 5, "b": 6, "constructor":{"prototype":{"bar":"baz"}} }'),
null
)
t.end()
})
t.test('sanitizes object string (no prototype key)', t => {
t.deepEqual(
j.safeParse('{"a": 5, "b": 6,"constructor":{"bar":"baz"} }'),
{ a: 5, b: 6, constructor: { bar: 'baz' } }
)
t.end()
})
t.end()
})
test('parse string with BOM', t => {
const theJson = { hello: 'world' }
const buffer = Buffer.concat([
Buffer.from([239, 187, 191]), // the utf8 BOM
Buffer.from(JSON.stringify(theJson))
])
t.deepEqual(j.parse(buffer.toString()), theJson)
t.end()
})
test('parse buffer with BOM', t => {
const theJson = { hello: 'world' }
const buffer = Buffer.concat([
Buffer.from([239, 187, 191]), // the utf8 BOM
Buffer.from(JSON.stringify(theJson))
])
t.deepEqual(j.parse(buffer), theJson)
t.end()
})
test('safeParse string with BOM', t => {
const theJson = { hello: 'world' }
const buffer = Buffer.concat([
Buffer.from([239, 187, 191]), // the utf8 BOM
Buffer.from(JSON.stringify(theJson))
])
t.deepEqual(j.safeParse(buffer.toString()), theJson)
t.end()
})
test('safeParse buffer with BOM', t => {
const theJson = { hello: 'world' }
const buffer = Buffer.concat([
Buffer.from([239, 187, 191]), // the utf8 BOM
Buffer.from(JSON.stringify(theJson))
])
t.deepEqual(j.safeParse(buffer), theJson)
t.end()
})
test('scan handles optional options', t => {
t.doesNotThrow(() => j.scan({ a: 'b' }))
t.end()
})
test('safe option', t => {
t.test('parse with safe=true returns null on __proto__', t => {
const text = '{ "a": 5, "b": 6, "__proto__": { "x": 7 } }'
t.strictEqual(j.parse(text, { safe: true }), null)
t.end()
})
t.test('parse with safe=true returns null on constructor', t => {
const text = '{ "a": 5, "b": 6, "constructor": {"prototype": {"bar": "baz"}} }'
t.strictEqual(j.parse(text, { safe: true }), null)
t.end()
})
t.test('parse with safe=true returns object when valid', t => {
const text = '{ "a": 5, "b": 6 }'
t.deepEqual(j.parse(text, { safe: true }), { a: 5, b: 6 })
t.end()
})
t.test('parse with safe=true and reviver', t => {
const text = '{ "a": 5, "b": 6, "__proto__": { "x": 7 } }'
const reviver = (_key, value) => {
return typeof value === 'number' ? value + 1 : value
}
t.strictEqual(j.parse(text, reviver, { safe: true }), null)
t.end()
})
t.test('parse with safe=true and protoAction=remove returns null', t => {
const text = '{ "a": 5, "b": 6, "__proto__": { "x": 7 } }'
t.strictEqual(j.parse(text, { safe: true, protoAction: 'remove' }), null)
t.end()
})
t.test('parse with safe=true and constructorAction=remove returns null', t => {
const text = '{ "a": 5, "b": 6, "constructor": {"prototype": {"bar": "baz"}} }'
t.strictEqual(j.parse(text, { safe: true, constructorAction: 'remove' }), null)
t.end()
})
t.test('parse with safe=false throws on __proto__', t => {
const text = '{ "a": 5, "b": 6, "__proto__": { "x": 7 } }'
t.throws(() => j.parse(text, { safe: false }), SyntaxError)
t.end()
})
t.test('parse with safe=false throws on constructor', t => {
const text = '{ "a": 5, "b": 6, "constructor": {"prototype": {"bar": "baz"}} }'
t.throws(() => j.parse(text, { safe: false }), SyntaxError)
t.end()
})
t.test('scan with safe=true returns null on __proto__', t => {
const obj = JSON.parse('{ "a": 5, "b": 6, "__proto__": { "x": 7 } }')
t.strictEqual(j.scan(obj, { safe: true }), null)
t.end()
})
t.test('scan with safe=true returns null on constructor', t => {
const obj = JSON.parse('{ "a": 5, "b": 6, "constructor": {"prototype": {"bar": "baz"}} }')
t.strictEqual(j.scan(obj, { safe: true }), null)
t.end()
})
t.test('scan with safe=true returns object when valid', t => {
const obj = { a: 5, b: 6 }
t.deepEqual(j.scan(obj, { safe: true }), { a: 5, b: 6 })
t.end()
})
t.test('scan with safe=false throws on __proto__', t => {
const obj = JSON.parse('{ "a": 5, "b": 6, "__proto__": { "x": 7 } }')
t.throws(() => j.scan(obj, { safe: false }), SyntaxError)
t.end()
})
t.test('scan with safe=false throws on constructor', t => {
const obj = JSON.parse('{ "a": 5, "b": 6, "constructor": {"prototype": {"bar": "baz"}} }')
t.throws(() => j.scan(obj, { safe: false }), SyntaxError)
t.end()
})
t.test('parse with safe=true returns null on nested __proto__', t => {
const text = '{ "a": 5, "c": { "d": 0, "__proto__": { "y": 8 } } }'
t.strictEqual(j.parse(text, { safe: true }), null)
t.end()
})
t.test('parse with safe=true returns null on nested constructor', t => {
const text = '{ "a": 5, "c": { "d": 0, "constructor": {"prototype": {"bar": "baz"}} } }'
t.strictEqual(j.parse(text, { safe: true }), null)
t.end()
})
t.test('parse with safe=true and protoAction=ignore returns object', t => {
const text = '{ "a": 5, "b": 6, "__proto__": { "x": 7 } }'
t.deepEqual(
j.parse(text, { safe: true, protoAction: 'ignore' }),
JSON.parse(text)
)
t.end()
})
t.test('parse with safe=true and constructorAction=ignore returns object', t => {
const text = '{ "a": 5, "b": 6, "constructor": {"prototype": {"bar": "baz"}} }'
t.deepEqual(
j.parse(text, { safe: true, constructorAction: 'ignore' }),
JSON.parse(text)
)
t.end()
})
t.test('should reset stackTraceLimit with safe option', t => {
const text = '{ "a": 5, "b": 6, "__proto__": { "x": 7 } }'
Error.stackTraceLimit = 42
t.strictEqual(j.parse(text, { safe: true }), null)
t.same(Error.stackTraceLimit, 42)
t.end()
})
t.end()
})

View File

@@ -0,0 +1,61 @@
'use strict'
const FakeTimers = require('@sinonjs/fake-timers')
const fs = require('fs')
const SonicBoom = require('../')
const { file, runTests } = require('./helper')
runTests(buildTests)
function buildTests (test, sync) {
// Reset the umask for testing
process.umask(0o000)
test('periodicflush_off', (t) => {
t.plan(4)
const clock = FakeTimers.install()
const dest = file()
const fd = fs.openSync(dest, 'w')
const stream = new SonicBoom({ fd, sync, minLength: 5000 })
t.ok(stream.write('hello world\n'))
setTimeout(function () {
fs.readFile(dest, 'utf8', function (err, data) {
t.error(err)
t.equal(data, '')
stream.destroy()
t.pass('file empty')
})
}, 2000)
clock.tick(2000)
clock.uninstall()
})
test('periodicflush_on', (t) => {
t.plan(4)
const clock = FakeTimers.install()
const dest = file()
const fd = fs.openSync(dest, 'w')
const stream = new SonicBoom({ fd, sync, minLength: 5000, periodicFlush: 1000 })
t.ok(stream.write('hello world\n'))
setTimeout(function () {
fs.readFile(dest, 'utf8', function (err, data) {
t.error(err)
t.equal(data, 'hello world\n')
stream.destroy()
t.pass('file not empty')
})
}, 2000)
clock.tick(2000)
clock.uninstall()
})
}

View File

@@ -0,0 +1,2 @@
export declare var DiagnosticCategory: any;
//# sourceMappingURL=diagnosticCategory.d.ts.map

View File

@@ -0,0 +1,55 @@
import Benchmark from "benchmark";
import { z } from "zod/v3";
const SUITE_NAME = "z.string";
const suite = new Benchmark.Suite(SUITE_NAME);
const empty = "";
const short = "short";
const long = "long".repeat(256);
const manual = (str: unknown) => {
if (typeof str !== "string") {
throw new Error("Not a string");
}
return str;
};
const stringSchema = z.string();
const optionalStringSchema = z.string().optional();
const optionalNullableStringSchema = z.string().optional().nullable();
suite
.add("empty string", () => {
stringSchema.parse(empty);
})
.add("short string", () => {
stringSchema.parse(short);
})
.add("long string", () => {
stringSchema.parse(long);
})
.add("optional string", () => {
optionalStringSchema.parse(long);
})
.add("nullable string", () => {
optionalNullableStringSchema.parse(long);
})
.add("nullable (null) string", () => {
optionalNullableStringSchema.parse(null);
})
.add("invalid: null", () => {
try {
stringSchema.parse(null);
} catch (_err) {}
})
.add("manual parser: long", () => {
manual(long);
})
.on("cycle", (e: Benchmark.Event) => {
console.log(`${SUITE_NAME}: ${e.target}`);
});
export default {
suites: [suite],
};

View File

@@ -0,0 +1,207 @@
[![npm version](https://img.shields.io/npm/v/eslint-scope.svg)](https://www.npmjs.com/package/eslint-scope)
[![Downloads](https://img.shields.io/npm/dm/eslint-scope.svg)](https://www.npmjs.com/package/eslint-scope)
[![Build Status](https://github.com/eslint/js/workflows/CI/badge.svg)](https://github.com/eslint/js/actions)
# ESLint Scope
ESLint Scope is the [ECMAScript](http://www.ecma-international.org/publications/standards/Ecma-262.htm) scope analyzer used in ESLint. It is a fork of [escope](http://github.com/estools/escope).
## Install
```
npm i eslint-scope --save
```
## 📖 Usage
To use in an ESM file:
```js
import * as eslintScope from "eslint-scope";
```
To use in a CommonJS file:
```js
const eslintScope = require("eslint-scope");
```
In order to analyze scope, you'll need to have an [ESTree](https://github.com/estree/estree) compliant AST structure to run it on. The primary method is `eslintScope.analyze()`, which takes two arguments:
1. `ast` - the ESTree-compliant AST structure to analyze.
2. `options` (optional) - Options to adjust how the scope is analyzed, including:
- `ignoreEval` (default: `false`) - Set to `true` to ignore all `eval()` calls (which would normally create scopes).
- `nodejsScope` (default: `false`) - Set to `true` to create a top-level function scope needed for CommonJS evaluation.
- `impliedStrict` (default: `false`) - Set to `true` to evaluate the code in strict mode even outside of modules and without `"use strict"`.
- `ecmaVersion` (default: `5`) - The version of ECMAScript to use to evaluate the code.
- `sourceType` (default: `"script"`) - The type of JavaScript file to evaluate. Change to `"module"` for ECMAScript module code.
- `childVisitorKeys` (default: `null`) - An object with visitor key information (like [`eslint-visitor-keys`](https://github.com/eslint/js/tree/main/packages/eslint-visitor-keys)). Without this, `eslint-scope` finds child nodes to visit algorithmically. Providing this option is a performance enhancement.
- `fallback` (default: `"iteration"`) - The strategy to use when `childVisitorKeys` is not specified. May be a function.
- `optimistic` (default: `false`) - Set to `true` to enable optimistic scope analysis.
- `jsx` (default: `false`) - Enables the tracking of JSX components as variable references.
Example:
```js
import * as eslintScope from "eslint-scope";
import * as espree from "espree";
import estraverse from "estraverse";
const options = {
ecmaVersion: 2022,
sourceType: "module",
};
const ast = espree.parse(code, { range: true, ...options });
const scopeManager = eslintScope.analyze(ast, options);
const currentScope = scopeManager.acquire(ast); // global scope
estraverse.traverse(ast, {
enter(node, parent) {
// do stuff
if (/Function/.test(node.type)) {
currentScope = scopeManager.acquire(node); // get current function scope
}
},
leave(node, parent) {
if (/Function/.test(node.type)) {
currentScope = currentScope.upper; // set to parent scope
}
// do stuff
},
});
```
## API
The following section describes the API for this package. You can also read [the docs](https://eslint.org/docs/latest/extend/scope-manager-interface).
### ScopeManager
The `ScopeManager` class is at the core of eslint-scope and is returned when you call `eslintScope.analyze()`. It manages all scopes in a given AST.
#### Properties
- `scopes` - An array of all scopes.
- `globalScope` - Reference to the global scope.
#### Methods
- **`addGlobals(names)`**
Adds variables to the global scope and resolves references to them.
- `names` - An array of strings, the names of variables to add to the global scope.
- Returns: `undefined`.
- **`acquire(node, inner)`**
Acquires the appropriate scope for a given node.
- `node` - The AST node to acquire the scope from.
- `inner` - Optional boolean. When `true`, returns the innermost scope, otherwise returns the outermost scope. Default is `false`.
- Returns: The acquired scope or `null` if no scope is found.
- **`acquireAll(node)` (Deprecated)**
Acquires all scopes for a given node.
- `node` - The AST node to acquire scopes from.
- Returns: An array of scopes or `undefined` if none are found.
- **`release(node, inner)`**
Returns the upper scope for a given node.
- `node` - The AST node to release.
- `inner` - Optional boolean. When `true`, returns the innermost upper scope, otherwise returns the outermost upper scope. Default is `false`.
- Returns: The upper scope or `null` if no upper scope exists.
- **`getDeclaredVariables(node)`**
Get variables that are declared by the node.
- `node` - The AST node to get declarations from.
- Returns: An array of variable objects declared by the node. If the node doesn't declare any variables, it returns an empty array.
- **`isGlobalReturn()`**
Determines if the global return statement should be allowed.
- Returns: `true` if the global return is enabled.
- **`isModule()` (Deprecated)**
Checks if the code should be handled as an ECMAScript module.
- Returns: `true` if the sourceType is "module".
- **`isImpliedStrict()` (Deprecated)**
Checks if implied strict mode is enabled.
- Returns: `true` if implied strict mode is enabled.
- **`isStrictModeSupported()` (Deprecated)**
Checks if strict mode is supported based on ECMAScript version.
- Returns: `true` if the ECMAScript version supports strict mode.
### Scope Objects
Scopes returned by the ScopeManager methods have the following properties:
- `type` - The type of scope (e.g., `"function"`, `"block"`, `"global"`).
- `isStrict` - `true` if this scope is in strict mode.
- `variables` - Array of variables declared in this scope.
- `set` - A Map of variable names to Variable objects for variables declared in this scope.
- `references` - Array of references in this scope.
- `through` - Array of references in this scope and its child scopes that aren't resolved in this scope or its child scopes.
- `functionExpressionScope` - `true` if this is a `"function-expression-name"` scope.
- `variableScope` - Reference to the closest variable scope.
- `upper` - Reference to the parent scope.
- `childScopes` - Array of child scopes.
- `block` - The AST node that created this scope.
### GlobalScope
The `GlobalScope` class is a specialized scope representing the global execution context. It extends the base `Scope` class with additional functionality for handling implicitly defined global variables.
#### Properties
- **`implicit`** - Tracks implicitly defined global variables (those used without declaration).
- `set` - A Map of variable names to Variable objects for implicitly defined globals.
- `variables` - Array of implicit global Variable objects.
- `left` - Array of References that need to be linked to the variable they refer to.
### Variable Objects
Each variable object has the following properties:
- `name` - The variable name.
- `identifiers` - Array of identifier nodes declaring this variable.
- `references` - Array of references to this variable.
- `defs` - Array of definition objects for this variable.
- `scope` - The scope object where this variable is defined.
## Contributing
Issues and pull requests will be triaged and responded to as quickly as possible. We operate under the [ESLint Contributor Guidelines](http://eslint.org/docs/developer-guide/contributing), so please be sure to read them before contributing. If you're not sure where to dig in, check out the [issues](https://github.com/eslint/js/issues).
## Security Policy
We work hard to ensure that ESLint Scope is safe for everyone and that security issues are addressed quickly and responsibly. Read the full [security policy](https://github.com/eslint/.github/blob/master/SECURITY.md).
## Build Commands
- `npm test` - run all linting and tests
- `npm run lint` - run all linting
## License
ESLint Scope is licensed under a permissive BSD 2-clause license.
<!-- NOTE: This section is autogenerated. Do not manually edit.-->
<!--sponsorsstart-->
## Sponsors
The following companies, organizations, and individuals support ESLint's ongoing maintenance and development. [Become a Sponsor](https://eslint.org/donate)
to get your logo on our READMEs and [website](https://eslint.org/sponsors).
<h3>Platinum Sponsors</h3>
<p><a href="https://automattic.com"><img src="https://images.opencollective.com/automattic/d0ef3e1/logo.png" alt="Automattic" height="128"></a></p><h3>Gold Sponsors</h3>
<p><a href="https://qlty.sh/"><img src="https://images.opencollective.com/qltysh/33d157d/logo.png" alt="Qlty Software" height="96"></a></p><h3>Silver Sponsors</h3>
<p><a href="https://vite.dev/"><img src="https://images.opencollective.com/vite/d472863/logo.png" alt="Vite" height="64"></a> <a href="https://liftoff.io/"><img src="https://images.opencollective.com/liftoff/2d6c3b6/logo.png" alt="Liftoff" height="64"></a> <a href="https://stackblitz.com"><img src="https://avatars.githubusercontent.com/u/28635252" alt="StackBlitz" height="64"></a></p><h3>Bronze Sponsors</h3>
<p><a href="https://cybozu.co.jp/"><img src="https://images.opencollective.com/cybozu/933e46d/logo.png" alt="Cybozu" height="32"></a> <a href="https://opensource.sap.com"><img src="https://avatars.githubusercontent.com/u/2531208" alt="SAP" height="32"></a> <a href="https://www.crawljobs.com/"><img src="https://images.opencollective.com/crawljobs-poland/fa43a17/logo.png" alt="CrawlJobs" height="32"></a> <a href="https://depot.dev"><img src="https://images.opencollective.com/depot/39125a1/logo.png" alt="Depot" height="32"></a> <a href="https://www.n-ix.com/"><img src="https://images.opencollective.com/n-ix-ltd/575a7a5/logo.png" alt="N-iX Ltd" height="32"></a> <a href="https://icons8.com/"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8" height="32"></a> <a href="https://discord.com"><img src="https://images.opencollective.com/discordapp/f9645d9/logo.png" alt="Discord" height="32"></a> <a href="https://www.gitbook.com"><img src="https://avatars.githubusercontent.com/u/7111340" alt="GitBook" height="32"></a> <a href="https://herocoders.com"><img src="https://avatars.githubusercontent.com/u/37549774" alt="HeroCoders" height="32"></a> <a href="https://www.lambdatest.com"><img src="https://avatars.githubusercontent.com/u/171592363" alt="TestMu AI Open Source Office (Formerly LambdaTest)" height="32"></a></p>
<h3>Technology Sponsors</h3>
Technology sponsors allow us to use their products and services for free as part of a contribution to the open source ecosystem and our work.
<p><a href="https://netlify.com"><img src="https://raw.githubusercontent.com/eslint/eslint.org/main/src/assets/images/techsponsors/netlify-icon.svg" alt="Netlify" height="32"></a> <a href="https://algolia.com"><img src="https://raw.githubusercontent.com/eslint/eslint.org/main/src/assets/images/techsponsors/algolia-icon.svg" alt="Algolia" height="32"></a> <a href="https://1password.com"><img src="https://raw.githubusercontent.com/eslint/eslint.org/main/src/assets/images/techsponsors/1password-icon.svg" alt="1Password" height="32"></a></p>
<!--sponsorsend-->

View File

@@ -0,0 +1,665 @@
import MagicString from 'magic-string';
import { e as esmWalker } from './chunk-automock.js';
// AST walker module for ESTree compatible trees
function makeTest(test) {
if (typeof test === "string")
{ return function (type) { return type === test; } }
else if (!test)
{ return function () { return true; } }
else
{ return test }
}
var Found = function Found(node, state) { this.node = node; this.state = state; };
// Find the innermost node of a given type that contains the given
// position. Interface similar to findNodeAt.
function findNodeAround(node, pos, test, baseVisitor, state) {
test = makeTest(test);
if (!baseVisitor) { baseVisitor = base; }
try {
(function c(node, st, override) {
var type = override || node.type;
if (node.start > pos || node.end < pos) { return }
visitNode(baseVisitor, type, node, st, c);
if (test(type, node)) { throw new Found(node, st) }
})(node, state);
} catch (e) {
if (e instanceof Found) { return e }
throw e
}
}
function skipThrough(node, st, c) { c(node, st); }
function ignore(_node, _st, _c) {}
function visitNode(baseVisitor, type, node, st, c) {
if (baseVisitor[type] == null) { throw new Error(("No walker function defined for node type " + type)) }
baseVisitor[type](node, st, c);
}
// Node walkers.
var base = {};
base.Program = base.BlockStatement = base.StaticBlock = function (node, st, c) {
for (var i = 0, list = node.body; i < list.length; i += 1)
{
var stmt = list[i];
c(stmt, st, "Statement");
}
};
base.Statement = skipThrough;
base.EmptyStatement = ignore;
base.ExpressionStatement = base.ParenthesizedExpression = base.ChainExpression =
function (node, st, c) { return c(node.expression, st, "Expression"); };
base.IfStatement = function (node, st, c) {
c(node.test, st, "Expression");
c(node.consequent, st, "Statement");
if (node.alternate) { c(node.alternate, st, "Statement"); }
};
base.LabeledStatement = function (node, st, c) { return c(node.body, st, "Statement"); };
base.BreakStatement = base.ContinueStatement = ignore;
base.WithStatement = function (node, st, c) {
c(node.object, st, "Expression");
c(node.body, st, "Statement");
};
base.SwitchStatement = function (node, st, c) {
c(node.discriminant, st, "Expression");
for (var i = 0, list = node.cases; i < list.length; i += 1) {
var cs = list[i];
c(cs, st);
}
};
base.SwitchCase = function (node, st, c) {
if (node.test) { c(node.test, st, "Expression"); }
for (var i = 0, list = node.consequent; i < list.length; i += 1)
{
var cons = list[i];
c(cons, st, "Statement");
}
};
base.ReturnStatement = base.YieldExpression = base.AwaitExpression = function (node, st, c) {
if (node.argument) { c(node.argument, st, "Expression"); }
};
base.ThrowStatement = base.SpreadElement =
function (node, st, c) { return c(node.argument, st, "Expression"); };
base.TryStatement = function (node, st, c) {
c(node.block, st, "Statement");
if (node.handler) { c(node.handler, st); }
if (node.finalizer) { c(node.finalizer, st, "Statement"); }
};
base.CatchClause = function (node, st, c) {
if (node.param) { c(node.param, st, "Pattern"); }
c(node.body, st, "Statement");
};
base.WhileStatement = base.DoWhileStatement = function (node, st, c) {
c(node.test, st, "Expression");
c(node.body, st, "Statement");
};
base.ForStatement = function (node, st, c) {
if (node.init) { c(node.init, st, "ForInit"); }
if (node.test) { c(node.test, st, "Expression"); }
if (node.update) { c(node.update, st, "Expression"); }
c(node.body, st, "Statement");
};
base.ForInStatement = base.ForOfStatement = function (node, st, c) {
c(node.left, st, "ForInit");
c(node.right, st, "Expression");
c(node.body, st, "Statement");
};
base.ForInit = function (node, st, c) {
if (node.type === "VariableDeclaration") { c(node, st); }
else { c(node, st, "Expression"); }
};
base.DebuggerStatement = ignore;
base.FunctionDeclaration = function (node, st, c) { return c(node, st, "Function"); };
base.VariableDeclaration = function (node, st, c) {
for (var i = 0, list = node.declarations; i < list.length; i += 1)
{
var decl = list[i];
c(decl, st);
}
};
base.VariableDeclarator = function (node, st, c) {
c(node.id, st, "Pattern");
if (node.init) { c(node.init, st, "Expression"); }
};
base.Function = function (node, st, c) {
if (node.id) { c(node.id, st, "Pattern"); }
for (var i = 0, list = node.params; i < list.length; i += 1)
{
var param = list[i];
c(param, st, "Pattern");
}
c(node.body, st, node.expression ? "Expression" : "Statement");
};
base.Pattern = function (node, st, c) {
if (node.type === "Identifier")
{ c(node, st, "VariablePattern"); }
else if (node.type === "MemberExpression")
{ c(node, st, "MemberPattern"); }
else
{ c(node, st); }
};
base.VariablePattern = ignore;
base.MemberPattern = skipThrough;
base.RestElement = function (node, st, c) { return c(node.argument, st, "Pattern"); };
base.ArrayPattern = function (node, st, c) {
for (var i = 0, list = node.elements; i < list.length; i += 1) {
var elt = list[i];
if (elt) { c(elt, st, "Pattern"); }
}
};
base.ObjectPattern = function (node, st, c) {
for (var i = 0, list = node.properties; i < list.length; i += 1) {
var prop = list[i];
if (prop.type === "Property") {
if (prop.computed) { c(prop.key, st, "Expression"); }
c(prop.value, st, "Pattern");
} else if (prop.type === "RestElement") {
c(prop.argument, st, "Pattern");
}
}
};
base.Expression = skipThrough;
base.ThisExpression = base.Super = base.MetaProperty = ignore;
base.ArrayExpression = function (node, st, c) {
for (var i = 0, list = node.elements; i < list.length; i += 1) {
var elt = list[i];
if (elt) { c(elt, st, "Expression"); }
}
};
base.ObjectExpression = function (node, st, c) {
for (var i = 0, list = node.properties; i < list.length; i += 1)
{
var prop = list[i];
c(prop, st);
}
};
base.FunctionExpression = base.ArrowFunctionExpression = base.FunctionDeclaration;
base.SequenceExpression = function (node, st, c) {
for (var i = 0, list = node.expressions; i < list.length; i += 1)
{
var expr = list[i];
c(expr, st, "Expression");
}
};
base.TemplateLiteral = function (node, st, c) {
for (var i = 0, list = node.quasis; i < list.length; i += 1)
{
var quasi = list[i];
c(quasi, st);
}
for (var i$1 = 0, list$1 = node.expressions; i$1 < list$1.length; i$1 += 1)
{
var expr = list$1[i$1];
c(expr, st, "Expression");
}
};
base.TemplateElement = ignore;
base.UnaryExpression = base.UpdateExpression = function (node, st, c) {
c(node.argument, st, "Expression");
};
base.BinaryExpression = base.LogicalExpression = function (node, st, c) {
c(node.left, st, "Expression");
c(node.right, st, "Expression");
};
base.AssignmentExpression = base.AssignmentPattern = function (node, st, c) {
c(node.left, st, "Pattern");
c(node.right, st, "Expression");
};
base.ConditionalExpression = function (node, st, c) {
c(node.test, st, "Expression");
c(node.consequent, st, "Expression");
c(node.alternate, st, "Expression");
};
base.NewExpression = base.CallExpression = function (node, st, c) {
c(node.callee, st, "Expression");
if (node.arguments)
{ for (var i = 0, list = node.arguments; i < list.length; i += 1)
{
var arg = list[i];
c(arg, st, "Expression");
} }
};
base.MemberExpression = function (node, st, c) {
c(node.object, st, "Expression");
if (node.computed) { c(node.property, st, "Expression"); }
};
base.ExportNamedDeclaration = base.ExportDefaultDeclaration = function (node, st, c) {
if (node.declaration)
{ c(node.declaration, st, node.type === "ExportNamedDeclaration" || node.declaration.id ? "Statement" : "Expression"); }
if (node.source) { c(node.source, st, "Expression"); }
if (node.attributes)
{ for (var i = 0, list = node.attributes; i < list.length; i += 1)
{
var attr = list[i];
c(attr, st);
} }
};
base.ExportAllDeclaration = function (node, st, c) {
if (node.exported)
{ c(node.exported, st); }
c(node.source, st, "Expression");
if (node.attributes)
{ for (var i = 0, list = node.attributes; i < list.length; i += 1)
{
var attr = list[i];
c(attr, st);
} }
};
base.ImportAttribute = function (node, st, c) {
c(node.value, st, "Expression");
};
base.ImportDeclaration = function (node, st, c) {
for (var i = 0, list = node.specifiers; i < list.length; i += 1)
{
var spec = list[i];
c(spec, st);
}
c(node.source, st, "Expression");
if (node.attributes)
{ for (var i$1 = 0, list$1 = node.attributes; i$1 < list$1.length; i$1 += 1)
{
var attr = list$1[i$1];
c(attr, st);
} }
};
base.ImportExpression = function (node, st, c) {
c(node.source, st, "Expression");
if (node.options) { c(node.options, st, "Expression"); }
};
base.ImportSpecifier = base.ImportDefaultSpecifier = base.ImportNamespaceSpecifier = base.Identifier = base.PrivateIdentifier = base.Literal = ignore;
base.TaggedTemplateExpression = function (node, st, c) {
c(node.tag, st, "Expression");
c(node.quasi, st, "Expression");
};
base.ClassDeclaration = base.ClassExpression = function (node, st, c) { return c(node, st, "Class"); };
base.Class = function (node, st, c) {
if (node.id) { c(node.id, st, "Pattern"); }
if (node.superClass) { c(node.superClass, st, "Expression"); }
c(node.body, st);
};
base.ClassBody = function (node, st, c) {
for (var i = 0, list = node.body; i < list.length; i += 1)
{
var elt = list[i];
c(elt, st);
}
};
base.MethodDefinition = base.PropertyDefinition = base.Property = function (node, st, c) {
if (node.computed) { c(node.key, st, "Expression"); }
if (node.value) { c(node.value, st, "Expression"); }
};
const API_NOT_FOUND_ERROR = `There are some problems in resolving the mocks API.
You may encounter this issue when importing the mocks API from another module other than 'vitest'.
To fix this issue you can either:
- import the mocks API directly from 'vitest'
- enable the 'globals' option`;
function API_NOT_FOUND_CHECK(names) {
return `\nif (${names.map((name) => `typeof globalThis["${name}"] === "undefined"`).join(" && ")}) ` + `{ throw new Error(${JSON.stringify(API_NOT_FOUND_ERROR)}) }\n`;
}
function isIdentifier(node) {
return node.type === "Identifier";
}
function getNodeTail(code, node) {
let end = node.end;
if (code[node.end] === ";") {
end += 1;
}
if (code[node.end] === "\n") {
return end + 1;
}
if (code[node.end + 1] === "\n") {
end += 1;
}
return end;
}
const regexpHoistable = /\b(?:vi|vitest)\s*\.\s*(?:mock|unmock|hoisted|doMock|doUnmock)\s*\(/;
const hashbangRE = /^#!.*\n/;
// Public redistributions of Vitest that re-export its mocking API (`vi`)
// verbatim under their own specifier. Imports from these are treated as the
// hoisted module so `vi.mock()` is hoisted for e.g.
// `import { vi } from 'vite-plus/test'`, exactly as it is for `vitest`.
const REDISTRIBUTED_HOISTED_MODULES = ["vite-plus/test"];
// this is a fork of Vite SSR transform
function hoistMocks(code, id, parse, options = {}) {
const needHoisting = (options.regexpHoistable || regexpHoistable).test(code);
if (!needHoisting) {
return;
}
const s = options.magicString?.() || new MagicString(code);
let ast;
try {
ast = parse(code);
} catch (err) {
console.error(`Cannot parse ${id}:\n${err.message}.`);
return;
}
const { hoistableMockMethodNames = ["mock", "unmock"], dynamicImportMockMethodNames = [
"mock",
"unmock",
"doMock",
"doUnmock"
], hoistedMethodNames = ["hoisted"], utilsObjectNames = ["vi", "vitest"], hoistedModule = "vitest" } = options;
// hoist at the start of the file, after the hashbang
const hashbangEnd = hashbangRE.exec(code)?.[0].length ?? 0;
let hoistIndex = hashbangEnd;
let hoistedModuleImported = false;
let uid = 0;
const idToImportMap = new Map();
const imports = [];
// this will transform import statements into dynamic ones, if there are imports
// it will keep the import as is, if we don't need to mock anything
// in browser environment it will wrap the module value with "vitest_wrap_module" function
// that returns a proxy to the module so that named exports can be mocked
function defineImport(importNode) {
const source = importNode.source.value;
// always hoist vitest import to top of the file, so
// "vi" helpers can access it. Vitest redistributions that re-export the
// mocking API under their own specifier are recognized the same way.
if (hoistedModule === source || REDISTRIBUTED_HOISTED_MODULES.includes(source)) {
hoistedModuleImported = true;
return;
}
const importId = `__vi_import_${uid++}__`;
imports.push({
id: importId,
node: importNode
});
return importId;
}
// 1. check all import statements and record id -> importName map
for (const node of ast.body) {
// import foo from 'foo' --> foo -> __import_foo__.default
// import { baz } from 'foo' --> baz -> __import_foo__.baz
// import * as ok from 'foo' --> ok -> __import_foo__
if (node.type === "ImportDeclaration") {
const importId = defineImport(node);
if (!importId) {
continue;
}
for (const spec of node.specifiers) {
if (spec.type === "ImportSpecifier") {
if (spec.imported.type === "Identifier") {
idToImportMap.set(spec.local.name, `${importId}.${spec.imported.name}`);
} else {
idToImportMap.set(spec.local.name, `${importId}[${JSON.stringify(spec.imported.value)}]`);
}
} else if (spec.type === "ImportDefaultSpecifier") {
idToImportMap.set(spec.local.name, `${importId}.default`);
} else {
// namespace specifier
idToImportMap.set(spec.local.name, importId);
}
}
}
}
const declaredConst = new Set();
const hoistedNodes = new Set();
function createSyntaxError(node, message) {
const _error = new SyntaxError(message);
Error.captureStackTrace(_error, createSyntaxError);
const serializedError = {
name: "SyntaxError",
message: _error.message,
stack: _error.stack
};
if (options.codeFrameGenerator) {
serializedError.frame = options.codeFrameGenerator(node, id, code);
}
return serializedError;
}
function assertNotDefaultExport(node, error) {
const defaultExport = findNodeAround(ast, node.start, "ExportDefaultDeclaration")?.node;
if (defaultExport?.declaration === node || defaultExport?.declaration.type === "AwaitExpression" && defaultExport.declaration.argument === node) {
throw createSyntaxError(defaultExport, error);
}
}
function assertNotNamedExport(node, error) {
const nodeExported = findNodeAround(ast, node.start, "ExportNamedDeclaration")?.node;
if (nodeExported?.declaration === node) {
throw createSyntaxError(nodeExported, error);
}
}
function getVariableDeclaration(node) {
const declarationNode = findNodeAround(ast, node.start, "VariableDeclaration")?.node;
const init = declarationNode?.declarations[0]?.init;
if (init && (init === node || init.type === "AwaitExpression" && init.argument === node)) {
return declarationNode;
}
}
const usedUtilityExports = new Set();
let hasImportMetaVitest = false;
esmWalker(ast, {
onImportMeta(node) {
const property = code.slice(node.end, node.end + 7);
if (property === ".vitest") {
hasImportMetaVitest = true;
}
},
onIdentifier(id, info, parentStack) {
const binding = idToImportMap.get(id.name);
if (!binding) {
return;
}
if (info.hasBindingShortcut) {
s.appendLeft(id.end, `: ${binding}`);
} else if (info.classDeclaration) {
if (!declaredConst.has(id.name)) {
declaredConst.add(id.name);
// locate the top-most node containing the class declaration
const topNode = parentStack[parentStack.length - 2];
s.prependRight(topNode.start, `const ${id.name} = ${binding};\n`);
}
} else if (!info.classExpression) {
s.update(id.start, id.end, binding);
}
},
onDynamicImport(_node) {
// TODO: vi.mock(import) breaks it, and vi.mock('', () => import) also does,
// only move imports that are outside of vi.mock
// backwards compat, don't do if not passed
// if (!options.globalThisAccessor) {
// return
// }
// const globalThisAccessor = options.globalThisAccessor
// const replaceString = `globalThis[${globalThisAccessor}].wrapDynamicImport(() => import(`
// const importSubstring = code.substring(node.start, node.end)
// const hasIgnore = importSubstring.includes('/* @vite-ignore */')
// s.overwrite(
// node.start,
// (node.source as Positioned<Expression>).start,
// replaceString + (hasIgnore ? '/* @vite-ignore */ ' : ''),
// )
// s.overwrite(node.end - 1, node.end, '))')
},
onCallExpression(node) {
if (node.callee.type === "MemberExpression" && isIdentifier(node.callee.object) && utilsObjectNames.includes(node.callee.object.name) && isIdentifier(node.callee.property)) {
const methodName = node.callee.property.name;
usedUtilityExports.add(node.callee.object.name);
if (hoistableMockMethodNames.includes(methodName)) {
const method = `${node.callee.object.name}.${methodName}`;
assertNotDefaultExport(node, `Cannot export the result of "${method}". Remove export declaration because "${method}" doesn\'t return anything.`);
const declarationNode = getVariableDeclaration(node);
if (declarationNode) {
assertNotNamedExport(declarationNode, `Cannot export the result of "${method}". Remove export declaration because "${method}" doesn\'t return anything.`);
}
// rewrite vi.mock(import('..')) into vi.mock('..')
if (node.type === "CallExpression" && node.callee.type === "MemberExpression" && dynamicImportMockMethodNames.includes(node.callee.property.name)) {
const moduleInfo = node.arguments[0];
// vi.mock(import('./path')) -> vi.mock('./path')
if (moduleInfo.type === "ImportExpression") {
const source = moduleInfo.source;
s.overwrite(moduleInfo.start, moduleInfo.end, s.slice(source.start, source.end));
}
// vi.mock(await import('./path')) -> vi.mock('./path')
if (moduleInfo.type === "AwaitExpression" && moduleInfo.argument.type === "ImportExpression") {
const source = moduleInfo.argument.source;
s.overwrite(moduleInfo.start, moduleInfo.end, s.slice(source.start, source.end));
}
}
hoistedNodes.add(node);
} else if (dynamicImportMockMethodNames.includes(methodName)) {
const moduleInfo = node.arguments[0];
let source = null;
if (moduleInfo.type === "ImportExpression") {
source = moduleInfo.source;
}
if (moduleInfo.type === "AwaitExpression" && moduleInfo.argument.type === "ImportExpression") {
source = moduleInfo.argument.source;
}
if (source) {
s.overwrite(moduleInfo.start, moduleInfo.end, s.slice(source.start, source.end));
}
}
if (hoistedMethodNames.includes(methodName)) {
assertNotDefaultExport(node, "Cannot export hoisted variable. You can control hoisting behavior by placing the import from this file first.");
const declarationNode = getVariableDeclaration(node);
if (declarationNode) {
assertNotNamedExport(declarationNode, "Cannot export hoisted variable. You can control hoisting behavior by placing the import from this file first.");
// hoist "const variable = vi.hoisted(() => {})"
hoistedNodes.add(declarationNode);
} else {
const awaitedExpression = findNodeAround(ast, node.start, "AwaitExpression")?.node;
// hoist "await vi.hoisted(async () => {})" or "vi.hoisted(() => {})"
const moveNode = awaitedExpression?.argument === node ? awaitedExpression : node;
hoistedNodes.add(moveNode);
}
}
}
}
});
function getNodeName(node) {
const callee = node.callee || {};
if (callee.type === "MemberExpression" && isIdentifier(callee.property) && isIdentifier(callee.object)) {
const argument = node.arguments[0];
const argStr = argument.type === "Literal" || argument.type === "ImportExpression" ? code.slice(argument.start, argument.end) : "";
return `${callee.object.name}.${callee.property.name}(${argStr})`;
}
return "\"hoisted method\"";
}
function getNodeCall(node) {
if (node.type === "CallExpression") {
return node;
}
if (node.type === "VariableDeclaration") {
const { declarations } = node;
const init = declarations[0].init;
if (init) {
return getNodeCall(init);
}
}
if (node.type === "AwaitExpression") {
const { argument } = node;
if (argument.type === "CallExpression") {
return getNodeCall(argument);
}
}
return node;
}
function createError(outsideNode, insideNode) {
const outsideCall = getNodeCall(outsideNode);
const insideCall = getNodeCall(insideNode);
throw createSyntaxError(insideCall, `Cannot call ${getNodeName(insideCall)} inside ${getNodeName(outsideCall)}: both methods are hoisted to the top of the file and not actually called inside each other.`);
}
// validate hoistedNodes doesn't have nodes inside other nodes
const arrayNodes = Array.from(hoistedNodes);
for (let i = 0; i < arrayNodes.length; i++) {
const node = arrayNodes[i];
for (let j = i + 1; j < arrayNodes.length; j++) {
const otherNode = arrayNodes[j];
if (node.start >= otherNode.start && node.end <= otherNode.end) {
throw createError(otherNode, node);
}
if (otherNode.start >= node.start && otherNode.end <= node.end) {
throw createError(node, otherNode);
}
}
}
// validate that hoisted nodes are defined on the top level
// ignore `import.meta.vitest` because it needs to be inside an IfStatement
// and it can be used anywhere in the code (inside methods too)
if (!hasImportMetaVitest) {
for (const node of ast.body) {
hoistedNodes.delete(node);
if (node.type === "ExpressionStatement") {
hoistedNodes.delete(node.expression);
}
}
for (const invalidNode of hoistedNodes) {
console.warn(`Warning: A ${getNodeName(getNodeCall(invalidNode))} call in "${id}" is not at the top level of the module. ` + `Although it appears nested, it will be hoisted and executed before any tests run. ` + `Move it to the top level to reflect its actual execution order. This will become an error in a future version.\n` + `See: https://vitest.dev/guide/mocking/modules#how-it-works`);
}
}
// hoist vi.mock/vi.hoisted
for (const node of arrayNodes) {
const end = getNodeTail(code, node);
// don't hoist into itself if it's already at the top
if (hoistIndex === end || hoistIndex === node.start) {
hoistIndex = end;
} else {
s.move(node.start, end, hoistIndex);
}
}
// hoist actual dynamic imports last so they are inserted after all hoisted mocks
for (const { node: importNode, id: importId } of imports) {
const source = importNode.source.value;
const sourceString = JSON.stringify(source);
let importLine = `const ${importId} = await `;
if (options.globalThisAccessor) {
importLine += `globalThis[${options.globalThisAccessor}].wrapDynamicImport(() => import(${sourceString}));\n`;
} else {
importLine += `import(${sourceString});\n`;
}
s.update(importNode.start, importNode.end, importLine);
if (importNode.start === hoistIndex) {
// no need to hoist, but update hoistIndex to keep the order
hoistIndex = importNode.end;
} else {
// There will be an error if the module is called before it is imported,
// so the module import statement is hoisted to the top
s.move(importNode.start, importNode.end, hoistIndex);
}
}
if (!hoistedModuleImported && arrayNodes.length > 0) {
const utilityImports = [...usedUtilityExports];
// "vi" or "vitest" is imported from a module other than "vitest"
if (utilityImports.some((name) => idToImportMap.has(name))) {
s.appendLeft(hashbangEnd, API_NOT_FOUND_CHECK(utilityImports));
} else if (utilityImports.length) {
s.appendLeft(hashbangEnd, `import { ${[...usedUtilityExports].join(", ")} } from ${JSON.stringify(hoistedModule)}\n`);
}
}
return s;
}
export { hoistMocks as h };

View File

@@ -0,0 +1,2 @@
import type { LibDefinition } from '../variable';
export declare const es2018: LibDefinition;

View File

@@ -0,0 +1,12 @@
import type * as ts from 'typescript';
export declare enum AnyType {
Any = 0,
PromiseAny = 1,
AnyArray = 2,
Safe = 3
}
/**
* @returns `AnyType.Any` if the type is `any`, `AnyType.AnyArray` if the type is `any[]` or `readonly any[]`, `AnyType.PromiseAny` if the type is `Promise<any>`,
* otherwise it returns `AnyType.Safe`.
*/
export declare function discriminateAnyType(type: ts.Type, checker: ts.TypeChecker, program: ts.Program, tsNode: ts.Node): AnyType;

View File

@@ -0,0 +1,134 @@
<p align="center">
<a href="https://gulpjs.com">
<img height="257" width="114" src="https://raw.githubusercontent.com/gulpjs/artwork/master/gulp-2x.png">
</a>
</p>
# glob-parent
[![NPM version][npm-image]][npm-url] [![Downloads][downloads-image]][npm-url] [![Build Status][ci-image]][ci-url] [![Coveralls Status][coveralls-image]][coveralls-url]
Extract the non-magic parent path from a glob string.
## Usage
```js
var globParent = require('glob-parent');
globParent('path/to/*.js'); // 'path/to'
globParent('/root/path/to/*.js'); // '/root/path/to'
globParent('/*.js'); // '/'
globParent('*.js'); // '.'
globParent('**/*.js'); // '.'
globParent('path/{to,from}'); // 'path'
globParent('path/!(to|from)'); // 'path'
globParent('path/?(to|from)'); // 'path'
globParent('path/+(to|from)'); // 'path'
globParent('path/*(to|from)'); // 'path'
globParent('path/@(to|from)'); // 'path'
globParent('path/**/*'); // 'path'
// if provided a non-glob path, returns the nearest dir
globParent('path/foo/bar.js'); // 'path/foo'
globParent('path/foo/'); // 'path/foo'
globParent('path/foo'); // 'path' (see issue #3 for details)
```
## API
### `globParent(maybeGlobString, [options])`
Takes a string and returns the part of the path before the glob begins. Be aware of Escaping rules and Limitations below.
#### options
```js
{
// Disables the automatic conversion of slashes for Windows
flipBackslashes: true;
}
```
## Escaping
The following characters have special significance in glob patterns and must be escaped if you want them to be treated as regular path characters:
- `?` (question mark) unless used as a path segment alone
- `*` (asterisk)
- `|` (pipe)
- `(` (opening parenthesis)
- `)` (closing parenthesis)
- `{` (opening curly brace)
- `}` (closing curly brace)
- `[` (opening bracket)
- `]` (closing bracket)
**Example**
```js
globParent('foo/[bar]/'); // 'foo'
globParent('foo/\\[bar]/'); // 'foo/[bar]'
```
## Limitations
### Braces & Brackets
This library attempts a quick and imperfect method of determining which path
parts have glob magic without fully parsing/lexing the pattern. There are some
advanced use cases that can trip it up, such as nested braces where the outer
pair is escaped and the inner one contains a path separator. If you find
yourself in the unlikely circumstance of being affected by this or need to
ensure higher-fidelity glob handling in your library, it is recommended that you
pre-process your input with [expand-braces] and/or [expand-brackets].
### Windows
Backslashes are not valid path separators for globs. If a path with backslashes
is provided anyway, for simple cases, glob-parent will replace the path
separator for you and return the non-glob parent path (now with
forward-slashes, which are still valid as Windows path separators).
This cannot be used in conjunction with escape characters.
```js
// BAD
globParent('C:\\Program Files \\(x86\\)\\*.ext'); // 'C:/Program Files /(x86/)'
// GOOD
globParent('C:/Program Files\\(x86\\)/*.ext'); // 'C:/Program Files (x86)'
```
If you are using escape characters for a pattern without path parts (i.e.
relative to `cwd`), prefix with `./` to avoid confusing glob-parent.
```js
// BAD
globParent('foo \\[bar]'); // 'foo '
globParent('foo \\[bar]*'); // 'foo '
// GOOD
globParent('./foo \\[bar]'); // 'foo [bar]'
globParent('./foo \\[bar]*'); // '.'
```
## License
ISC
<!-- prettier-ignore-start -->
[downloads-image]: https://img.shields.io/npm/dm/glob-parent.svg?style=flat-square
[npm-url]: https://www.npmjs.com/package/glob-parent
[npm-image]: https://img.shields.io/npm/v/glob-parent.svg?style=flat-square
[ci-url]: https://github.com/gulpjs/glob-parent/actions?query=workflow:dev
[ci-image]: https://img.shields.io/github/workflow/status/gulpjs/glob-parent/dev?style=flat-square
[coveralls-url]: https://coveralls.io/r/gulpjs/glob-parent
[coveralls-image]: https://img.shields.io/coveralls/gulpjs/glob-parent/master.svg?style=flat-square
<!-- prettier-ignore-end -->
<!-- prettier-ignore-start -->
[expand-braces]: https://github.com/jonschlinkert/expand-braces
[expand-brackets]: https://github.com/jonschlinkert/expand-brackets
<!-- prettier-ignore-end -->

View File

@@ -0,0 +1,162 @@
<h1 align=center>
<a href="http://chaijs.com" title="Chai Documentation">
<img alt="ChaiJS" src="http://chaijs.com/img/chai-logo.png">
</a>
<br>
chai
</h1>
<p align=center>
Chai is a BDD / TDD assertion library for <a href="http://nodejs.org">node</a> and the browser that can be delightfully paired with any javascript testing framework.
</p>
<p align=center>
<a href="https://www.npmjs.com/package/chai">
<img
alt="downloads:?"
src="https://img.shields.io/npm/dm/chai.svg?style=flat-square"
/>
</a>
<a href="https://www.npmjs.com/package/chai">
<img
alt="node:?"
src="https://img.shields.io/badge/node-%3E=18.0-blue.svg?style=flat-square"
/>
</a>
<br/>
<a href="https://chai-slack.herokuapp.com/">
<img
alt="Join the Slack chat"
src="https://img.shields.io/badge/slack-join%20chat-E2206F.svg?style=flat-square"
/>
</a>
<a href="https://gitter.im/chaijs/chai">
<img
alt="Join the Gitter chat"
src="https://img.shields.io/badge/gitter-join%20chat-D0104D.svg?style=flat-square"
/>
</a>
<a href="https://opencollective.com/chaijs">
<img
alt="OpenCollective Backers"
src="https://opencollective.com/chaijs/backers/badge.svg?style=flat-square"
/>
</a>
</p>
For more information or to download plugins, view the [documentation](http://chaijs.com).
## What is Chai?
Chai is an _assertion library_, similar to Node's built-in `assert`. It makes testing much easier by giving you lots of assertions you can run against your code.
## Installation
### Node.js
`chai` is available on [npm](http://npmjs.org). To install it, type:
$ npm install --save-dev chai
### Browsers
You can also use it within the browser; install via npm and use the `index.js` file found within the download. For example:
```html
<script src="./node_modules/chai/index.js" type="module"></script>
```
## Usage
Import the library in your code, and then pick one of the styles you'd like to use - either `assert`, `expect` or `should`:
```js
import { assert } from 'chai'; // Using Assert style
import { expect } from 'chai'; // Using Expect style
import { should } from 'chai'; // Using Should style
```
### Register the chai testing style globally
```js
import 'chai/register-assert'; // Using Assert style
import 'chai/register-expect'; // Using Expect style
import 'chai/register-should'; // Using Should style
```
### Import assertion styles as local variables
```js
import { assert } from 'chai'; // Using Assert style
import { expect } from 'chai'; // Using Expect style
import { should } from 'chai'; // Using Should style
should(); // Modifies `Object.prototype`
import { expect, use } from 'chai'; // Creates local variables `expect` and `use`; useful for plugin use
```
### Usage with Mocha
```bash
mocha spec.js --require chai/register-assert.js # Using Assert style
mocha spec.js --require chai/register-expect.js # Using Expect style
mocha spec.js --require chai/register-should.js # Using Should style
```
[Read more about these styles in our docs](http://chaijs.com/guide/styles/).
## Plugins
Chai offers a robust Plugin architecture for extending Chai's assertions and interfaces.
- Need a plugin? View the [official plugin list](http://chaijs.com/plugins).
- Want to build a plugin? Read the [plugin api documentation](http://chaijs.com/guide/plugins/).
- Have a plugin and want it listed? Simply add the following keywords to your package.json:
- `chai-plugin`
- `browser` if your plugin works in the browser as well as Node.js
- `browser-only` if your plugin does not work with Node.js
### Related Projects
- [chaijs / chai-docs](https://github.com/chaijs/chai-docs): The chaijs.com website source code.
- [chaijs / assertion-error](https://github.com/chaijs/assertion-error): Custom `Error` constructor thrown upon an assertion failing.
- [chaijs / deep-eql](https://github.com/chaijs/deep-eql): Improved deep equality testing for Node.js and the browser.
- [chaijs / check-error](https://github.com/chaijs/check-error): Error comparison and information related utility for Node.js and the browser.
- [chaijs / loupe](https://github.com/chaijs/loupe): Inspect utility for Node.js and browsers.
- [chaijs / pathval](https://github.com/chaijs/pathval): Object value retrieval given a string path.
### Contributing
Thank you very much for considering to contribute!
Please make sure you follow our [Code Of Conduct](https://github.com/chaijs/chai/blob/master/CODE_OF_CONDUCT.md) and we also strongly recommend reading our [Contributing Guide](https://github.com/chaijs/chai/blob/master/CONTRIBUTING.md).
Here are a few issues other contributors frequently ran into when opening pull requests:
- Please do not commit changes to the `chai.js` build. We do it once per release.
- Before pushing your commits, please make sure you [rebase](https://github.com/chaijs/chai/blob/master/CONTRIBUTING.md#pull-requests) them.
### Contributors
Please see the full
[Contributors Graph](https://github.com/chaijs/chai/graphs/contributors) for our
list of contributors.
### Core Contributors
Feel free to reach out to any of the core contributors with your questions or
concerns. We will do our best to respond in a timely manner.
[![Keith Cirkel](https://avatars3.githubusercontent.com/u/118266?v=3&s=50)](https://github.com/keithamus)
[![James Garbutt](https://avatars3.githubusercontent.com/u/5677153?v=3&s=50)](https://github.com/43081j)
[![Kristján Oddsson](https://avatars3.githubusercontent.com/u/318208?v=3&s=50)](https://github.com/koddsson)
### Core Contributor Alumni
This project would not be what it is without the contributions from our prior
core contributors, for whom we are forever grateful:
[![Jake Luer](https://avatars3.githubusercontent.com/u/58988?v=3&s=50)](https://github.com/logicalparadox)
[![Veselin Todorov](https://avatars3.githubusercontent.com/u/330048?v=3&s=50)](https://github.com/vesln)
[![Lucas Fernandes da Costa](https://avatars3.githubusercontent.com/u/6868147?v=3&s=50)](https://github.com/lucasfcosta)
[![Grant Snodgrass](https://avatars3.githubusercontent.com/u/17260989?v=3&s=50)](https://github.com/meeber)

View File

@@ -0,0 +1,268 @@
/**
* @fileoverview Rule to require object keys to be sorted
* @author Toru Nagashima
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("./utils/ast-utils"),
naturalCompare = require("natural-compare");
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* Gets the property name of the given `Property` node.
*
* - If the property's key is an `Identifier` node, this returns the key's name
* whether it's a computed property or not.
* - If the property has a static name, this returns the static name.
* - Otherwise, this returns null.
* @param {ASTNode} node The `Property` node to get.
* @returns {string|null} The property name or null.
* @private
*/
function getPropertyName(node) {
const staticName = astUtils.getStaticPropertyName(node);
if (staticName !== null) {
return staticName;
}
return node.key.name || null;
}
/**
* Functions which check that the given 2 names are in specific order.
*
* Postfix `I` is meant insensitive.
* Postfix `N` is meant natural.
* @private
*/
const isValidOrders = {
asc(a, b) {
return a <= b;
},
ascI(a, b) {
return a.toLowerCase() <= b.toLowerCase();
},
ascN(a, b) {
return naturalCompare(a, b) <= 0;
},
ascIN(a, b) {
return naturalCompare(a.toLowerCase(), b.toLowerCase()) <= 0;
},
desc(a, b) {
return isValidOrders.asc(b, a);
},
descI(a, b) {
return isValidOrders.ascI(b, a);
},
descN(a, b) {
return isValidOrders.ascN(b, a);
},
descIN(a, b) {
return isValidOrders.ascIN(b, a);
},
};
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../types').Rule.RuleModule} */
module.exports = {
meta: {
type: "suggestion",
defaultOptions: [
"asc",
{
allowLineSeparatedGroups: false,
caseSensitive: true,
ignoreComputedKeys: false,
minKeys: 2,
natural: false,
},
],
docs: {
description: "Require object keys to be sorted",
recommended: false,
frozen: true,
url: "https://eslint.org/docs/latest/rules/sort-keys",
},
schema: [
{
enum: ["asc", "desc"],
},
{
type: "object",
properties: {
caseSensitive: {
type: "boolean",
},
natural: {
type: "boolean",
},
minKeys: {
type: "integer",
minimum: 2,
},
allowLineSeparatedGroups: {
type: "boolean",
},
ignoreComputedKeys: {
type: "boolean",
},
},
additionalProperties: false,
},
],
messages: {
sortKeys:
"Expected object keys to be in {{natural}}{{insensitive}}{{order}}ending order. '{{thisName}}' should be before '{{prevName}}'.",
},
},
create(context) {
const [
order,
{
caseSensitive,
natural,
minKeys,
allowLineSeparatedGroups,
ignoreComputedKeys,
},
] = context.options;
const insensitive = !caseSensitive;
const isValidOrder =
isValidOrders[
order + (insensitive ? "I" : "") + (natural ? "N" : "")
];
// The stack to save the previous property's name for each object literals.
let stack = null;
const sourceCode = context.sourceCode;
return {
ObjectExpression(node) {
stack = {
upper: stack,
prevNode: null,
prevBlankLine: false,
prevName: null,
numKeys: node.properties.length,
};
},
"ObjectExpression:exit"() {
stack = stack.upper;
},
SpreadElement(node) {
if (node.parent.type === "ObjectExpression") {
stack.prevName = null;
}
},
Property(node) {
if (node.parent.type === "ObjectPattern") {
return;
}
if (ignoreComputedKeys && node.computed) {
stack.prevName = null; // reset sort
return;
}
const prevName = stack.prevName;
const numKeys = stack.numKeys;
const thisName = getPropertyName(node);
// Get tokens between current node and previous node
const tokens =
stack.prevNode &&
sourceCode.getTokensBetween(stack.prevNode, node, {
includeComments: true,
});
let isBlankLineBetweenNodes = stack.prevBlankLine;
if (tokens) {
// check blank line between tokens
tokens.forEach((token, index) => {
const previousToken = tokens[index - 1];
if (
previousToken &&
token.loc.start.line - previousToken.loc.end.line >
1
) {
isBlankLineBetweenNodes = true;
}
});
// check blank line between the current node and the last token
if (
!isBlankLineBetweenNodes &&
node.loc.start.line - tokens.at(-1).loc.end.line > 1
) {
isBlankLineBetweenNodes = true;
}
// check blank line between the first token and the previous node
if (
!isBlankLineBetweenNodes &&
tokens[0].loc.start.line - stack.prevNode.loc.end.line >
1
) {
isBlankLineBetweenNodes = true;
}
}
stack.prevNode = node;
if (thisName !== null) {
stack.prevName = thisName;
}
if (allowLineSeparatedGroups && isBlankLineBetweenNodes) {
stack.prevBlankLine = thisName === null;
return;
}
if (
prevName === null ||
thisName === null ||
numKeys < minKeys
) {
return;
}
if (!isValidOrder(prevName, thisName)) {
context.report({
node,
loc: node.key.loc,
messageId: "sortKeys",
data: {
thisName,
prevName,
order,
insensitive: insensitive ? "insensitive " : "",
natural: natural ? "natural " : "",
},
});
}
},
};
},
};