WIP: bootstrap and partial real Solana watcher implementation
This commit is contained in:
@@ -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-კოდირებული ველი");
|
||||
}
|
||||
});
|
||||
File diff suppressed because one or more lines are too long
@@ -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;
|
||||
@@ -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"}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
# utf-8-validate
|
||||
|
||||
[](https://www.npmjs.com/package/utf-8-validate)
|
||||
[](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)
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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[];
|
||||
@@ -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;
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"main": "../../cjs/_array_without_holes.cjs",
|
||||
"module": "../../esm/_array_without_holes.js"
|
||||
}
|
||||
@@ -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 };
|
||||
@@ -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"}
|
||||
@@ -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.
|
||||
@@ -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()
|
||||
})
|
||||
@@ -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()
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export declare var DiagnosticCategory: any;
|
||||
//# sourceMappingURL=diagnosticCategory.d.ts.map
|
||||
@@ -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],
|
||||
};
|
||||
@@ -0,0 +1,207 @@
|
||||
[](https://www.npmjs.com/package/eslint-scope)
|
||||
[](https://www.npmjs.com/package/eslint-scope)
|
||||
[](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-->
|
||||
@@ -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 };
|
||||
@@ -0,0 +1,2 @@
|
||||
import type { LibDefinition } from '../variable';
|
||||
export declare const es2018: LibDefinition;
|
||||
@@ -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;
|
||||
@@ -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 -->
|
||||
@@ -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.
|
||||
|
||||
[](https://github.com/keithamus)
|
||||
[](https://github.com/43081j)
|
||||
[](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:
|
||||
|
||||
[](https://github.com/logicalparadox)
|
||||
[](https://github.com/vesln)
|
||||
[](https://github.com/lucasfcosta)
|
||||
[](https://github.com/meeber)
|
||||
@@ -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 " : "",
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user