WIP: bootstrap and partial real Solana watcher implementation

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

View File

@@ -0,0 +1,21 @@
'use strict'
const build = require('pino-abstract-transport')
const { pipeline, Transform } = require('node:stream')
module.exports = (options) => {
return build(function (source) {
const myTransportStream = new Transform({
autoDestroy: true,
objectMode: true,
transform (chunk, enc, cb) {
chunk.service = 'pino'
this.push(JSON.stringify(chunk))
cb()
}
})
pipeline(source, myTransportStream, () => {})
return myTransportStream
}, {
enablePipelining: true
})
}

View File

@@ -0,0 +1,61 @@
{
"name": "borsh",
"version": "0.7.0",
"description": "Binary Object Representation Serializer for Hashing",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"files": [
"lib",
"LICENSE-APACHE",
"LICENSE-MIT.txt"
],
"scripts": {
"build": "tsc -p ./tsconfig.json",
"test": "jest test --runInBand",
"fuzz": "jsfuzz borsh-ts/test/fuzz/borsh-roundtrip.js borsh-ts/test/fuzz/corpus/",
"dev": "yarn build -w",
"pretest": "yarn build",
"lint": "eslint borsh-ts/**/*.ts",
"pretty": "prettier --write borsh-ts/**/*.ts package.json",
"pretty:check": "yarn prettier --loglevel error --check borsh-ts/**/*.ts package.json",
"fix": "eslint borsh-ts/**/*.ts --fix"
},
"repository": {
"type": "git",
"url": "git+https://github.com/near/borsh-js.git"
},
"keywords": [
"serializer",
"binary",
"serializer",
"deserializer",
"consistency",
"deterministic"
],
"author": "Near Inc",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/near/borsh-js/issues"
},
"homepage": "https://github.com/near/borsh-js#readme",
"devDependencies": {
"@types/babel__core": "^7.1.2",
"@types/babel__template": "^7.0.2",
"@types/bn.js": "^5.1.0",
"@types/node": "^12.7.3",
"@typescript-eslint/eslint-plugin": "^2.18.0",
"@typescript-eslint/parser": "^2.18.0",
"bs58": "^4.0.0",
"eslint": "^6.5.1",
"jest": "^26.0.1",
"js-sha256": "^0.9.0",
"jsfuzz": "^1.0.14",
"prettier": "^2.4.1",
"typescript": "^3.6.2"
},
"dependencies": {
"bn.js": "^5.2.0",
"bs58": "^4.0.0",
"text-encoding-utf-8": "^1.0.2"
}
}

View File

@@ -0,0 +1,104 @@
import { SyntaxKind } from "#enums/syntaxKind";
import { cloneNode, createNodeArray, createNumericLiteral, createStringLiteral, } from "./factory.generated.js";
import { visitEachChild } from "./visitor.js";
function isArray(value) {
// See: https://github.com/microsoft/TypeScript/issues/17002
return Array.isArray(value);
}
function forEachChildRecursively(rootNode, cbNode, cbNodes) {
const queue = gatherPossibleChildren(rootNode);
const parents = []; // tracks parent references for elements in queue
while (parents.length < queue.length) {
parents.push(rootNode);
}
while (queue.length !== 0) {
const current = queue.pop();
const parent = parents.pop();
if (isArray(current)) {
if (cbNodes) {
const res = cbNodes(current, parent);
if (res) {
if (res === "skip")
continue;
return res;
}
}
for (let i = current.length - 1; i >= 0; --i) {
queue.push(current[i]);
parents.push(parent);
}
}
else {
const res = cbNode(current, parent);
if (res) {
if (res === "skip")
continue;
return res;
}
if (current.kind >= SyntaxKind.FirstNode) {
// add children in reverse order to the queue, so popping gives the first child
for (const child of gatherPossibleChildren(current)) {
queue.push(child);
parents.push(current);
}
}
}
}
}
function gatherPossibleChildren(node) {
const children = [];
node.forEachChild(addWorkItem, addWorkItem); // By using a stack above and `unshift` here, we emulate a depth-first preorder traversal
return children;
function addWorkItem(n) {
children.unshift(n);
}
}
function setParentRecursive(rootNode) {
if (rootNode === undefined)
return rootNode;
forEachChildRecursively(rootNode, (child, parent) => {
child.parent = parent;
return undefined;
});
return rootNode;
}
function setTextRange(node, range) {
if (range) {
node.pos = range.pos;
node.end = range.end;
}
return node;
}
export function getSynthesizedDeepClone(node, includeTrivia = true) {
const clone = node && getSynthesizedDeepCloneWorker(node);
if (clone && !includeTrivia) {
clone.pos = -1;
clone.end = -1;
}
return setParentRecursive(clone);
}
export function getSynthesizedDeepClones(nodes, includeTrivia = true) {
if (nodes) {
const cloned = createNodeArray(nodes.map(n => getSynthesizedDeepClone(n, includeTrivia)), nodes.pos, nodes.end);
return cloned;
}
return nodes;
}
function getSynthesizedDeepCloneWorker(node) {
const visited = visitEachChild(node, n => getSynthesizedDeepCloneWorker(n));
if (visited === node) {
// Leaf node — visitEachChild returned the same node since there are no children.
// We need to explicitly clone it.
const clone = node.kind === SyntaxKind.StringLiteral
? createStringLiteral(node.text, node.tokenFlags)
: node.kind === SyntaxKind.NumericLiteral
? createNumericLiteral(node.text, node.tokenFlags)
: cloneNode(node);
return setTextRange(clone, node);
}
// visitEachChild already created a new node with visited children.
// Clear the parent since setParentRecursive will set it later.
visited.parent = undefined;
return visited;
}
//# sourceMappingURL=clone.js.map

View File

@@ -0,0 +1,100 @@
import { PromisifyAssertion, Tester, ExpectStatic } from '@vitest/expect';
import { Plugin } from '@vitest/pretty-format';
import { Test } from '@vitest/runner';
import { SnapshotState } from '@vitest/snapshot';
import { B as BenchmarkResult } from './benchmark.d.DAaHLpsq.js';
import { U as UserConsoleLog } from './traces.d.D2T_R8rx.js';
interface SnapshotMatcher<T> {
<U extends { [P in keyof T] : any }>(snapshot: Partial<U>, hint?: string): void;
(hint?: string): void;
}
interface InlineSnapshotMatcher<T> {
<U extends { [P in keyof T] : any }>(properties: Partial<U>, snapshot?: string, hint?: string): void;
(hint?: string): void;
}
declare module "@vitest/expect" {
interface MatcherState {
environment: string;
snapshotState: SnapshotState;
task?: Readonly<Test>;
}
interface ExpectPollOptions {
interval?: number;
timeout?: number;
message?: string;
}
interface ExpectStatic {
assert: Chai.AssertStatic;
unreachable: (message?: string) => never;
soft: <T>(actual: T, message?: string) => Assertion<T>;
poll: <T>(actual: () => T, options?: ExpectPollOptions) => PromisifyAssertion<Awaited<T>>;
addEqualityTesters: (testers: Array<Tester>) => void;
assertions: (expected: number) => void;
hasAssertions: () => void;
addSnapshotSerializer: (plugin: Plugin) => void;
}
interface Assertion<T> {
matchSnapshot: SnapshotMatcher<T>;
toMatchSnapshot: SnapshotMatcher<T>;
toMatchInlineSnapshot: InlineSnapshotMatcher<T>;
/**
* Checks that an error thrown by a function matches a previously recorded snapshot.
*
* @param hint - Optional custom error message.
*
* @example
* expect(functionWithError).toThrowErrorMatchingSnapshot();
*/
toThrowErrorMatchingSnapshot: (hint?: string) => void;
/**
* Checks that an error thrown by a function matches an inline snapshot within the test file.
* Useful for keeping snapshots close to the test code.
*
* @param snapshot - Optional inline snapshot string to match.
* @param hint - Optional custom error message.
*
* @example
* const throwError = () => { throw new Error('Error occurred') };
* expect(throwError).toThrowErrorMatchingInlineSnapshot(`"Error occurred"`);
*/
toThrowErrorMatchingInlineSnapshot: (snapshot?: string, hint?: string) => void;
/**
* Compares the received value to a snapshot saved in a specified file.
* Useful for cases where snapshot content is large or needs to be shared across tests.
*
* @param filepath - Path to the snapshot file.
* @param hint - Optional custom error message.
*
* @example
* await expect(largeData).toMatchFileSnapshot('path/to/snapshot.json');
*/
toMatchFileSnapshot: (filepath: string, hint?: string) => Promise<void>;
}
}
declare module "@vitest/runner" {
interface TestContext {
/**
* `expect` instance bound to the current test.
*
* This API is useful for running snapshot tests concurrently because global expect cannot track them.
*/
readonly expect: ExpectStatic;
/** @internal */
_local: boolean;
}
interface TaskMeta {
typecheck?: boolean;
benchmark?: boolean;
}
interface File {
prepareDuration?: number;
environmentLoad?: number;
}
interface TaskBase {
logs?: UserConsoleLog[];
}
interface TaskResult {
benchmark?: BenchmarkResult;
}
}

View File

@@ -0,0 +1,4 @@
function _checkPrivateRedeclaration(e, t) {
if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object");
}
export { _checkPrivateRedeclaration as default };

View File

@@ -0,0 +1,5 @@
import assertClassBrand from "./assertClassBrand.js";
function _classPrivateFieldGet2(s, a) {
return s.get(assertClassBrand(s, a));
}
export { _classPrivateFieldGet2 as default };

View File

@@ -0,0 +1 @@
{"version":3,"file":"compilerOptions.js","sourceRoot":"","sources":["../../src/api/compilerOptions.ts"],"names":[],"mappings":""}

View File

@@ -0,0 +1,4 @@
// This file can be required in Browserify and Node.js for automatic polyfill
// To use it: require('es6-promise/auto');
'use strict';
module.exports = require('./').polyfill();

View File

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

View File

@@ -0,0 +1,45 @@
"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 });
exports.checkNullishAndReport = checkNullishAndReport;
const type_utils_1 = require("@typescript-eslint/type-utils");
const ts_api_utils_1 = require("ts-api-utils");
const ts = __importStar(require("typescript"));
function checkNullishAndReport(context, parserServices, { requireNullish }, maybeNullishNodes, descriptor) {
if (!requireNullish ||
maybeNullishNodes.some(node => (0, ts_api_utils_1.unionConstituents)(parserServices.getTypeAtLocation(node)).some(t => (0, type_utils_1.isTypeFlagSet)(t, ts.TypeFlags.Null | ts.TypeFlags.Undefined)))) {
context.report(descriptor);
}
}

View File

@@ -0,0 +1,19 @@
{
"versions": [
{
"version": "*",
"target": {
"node": "supported"
},
"response": {
"type": "time-permitting"
},
"backing": {
"donations": [
"https://github.com/sponsors/abetomo",
"https://github.com/sponsors/shadowspawn"
]
}
}
]
}

View File

@@ -0,0 +1,4 @@
declare module 'fast-json-stable-stringify' {
function stringify(obj: any): string;
export = stringify;
}

View File

@@ -0,0 +1,80 @@
name: CI
on:
push:
paths-ignore:
- 'docs/**'
- '*.md'
pull_request:
paths-ignore:
- 'docs/**'
- '*.md'
# This allows a subsequently queued workflow run to interrupt previous runs
concurrency:
group: "${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}"
cancel-in-progress: true
jobs:
dependency-review:
name: Dependency Review
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Check out repo
uses: actions/checkout@v5
with:
persist-credentials: false
- name: Dependency review
uses: actions/dependency-review-action@v3
test:
name: Test
runs-on: ${{ matrix.os }}
permissions:
contents: read
strategy:
matrix:
node-version: [20, 22, 24]
os: [macos-latest, ubuntu-latest, windows-latest]
steps:
- name: Check out repo
uses: actions/checkout@v5
with:
persist-credentials: false
- name: Setup Node ${{ matrix.node-version }}
uses: actions/setup-node@v5
with:
node-version: ${{ matrix.node-version }}
- name: Restore cached dependencies
uses: actions/cache@v4
with:
path: node_modules
key: node-modules-${{ hashFiles('package.json') }}
- name: Install dependencies
run: npm i --ignore-scripts
- name: Run Tests
run: npm run test-ci
automerge:
name: Automerge Dependabot PRs
if: >
github.event_name == 'pull_request' &&
github.event.pull_request.user.login == 'dependabot[bot]'
needs: test
permissions:
pull-requests: write
contents: write
runs-on: ubuntu-latest
steps:
- uses: fastify/github-action-merge-dependabot@v3
with:
github-token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -0,0 +1,6 @@
export declare function merge(...sets: Array<string>): string;
export declare function subexp(str: string): string;
export declare function typeOf(o: any): string;
export declare function toUpperCase(str: string): string;
export declare function toArray(obj: any): Array<any>;
export declare function assign(target: object, source: any): any;

View File

@@ -0,0 +1,845 @@
"use strict";
// parse a single path portion
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.AST = void 0;
const brace_expressions_js_1 = require("./brace-expressions.js");
const unescape_js_1 = require("./unescape.js");
const types = new Set(['!', '?', '+', '*', '@']);
const isExtglobType = (c) => types.has(c);
const isExtglobAST = (c) => isExtglobType(c.type);
// Map of which extglob types can adopt the children of a nested extglob
//
// anything but ! can adopt a matching type:
// +(a|+(b|c)|d) => +(a|b|c|d)
// *(a|*(b|c)|d) => *(a|b|c|d)
// @(a|@(b|c)|d) => @(a|b|c|d)
// ?(a|?(b|c)|d) => ?(a|b|c|d)
//
// * can adopt anything, because 0 or repetition is allowed
// *(a|?(b|c)|d) => *(a|b|c|d)
// *(a|+(b|c)|d) => *(a|b|c|d)
// *(a|@(b|c)|d) => *(a|b|c|d)
//
// + can adopt @, because 1 or repetition is allowed
// +(a|@(b|c)|d) => +(a|b|c|d)
//
// + and @ CANNOT adopt *, because 0 would be allowed
// +(a|*(b|c)|d) => would match "", on *(b|c)
// @(a|*(b|c)|d) => would match "", on *(b|c)
//
// + and @ CANNOT adopt ?, because 0 would be allowed
// +(a|?(b|c)|d) => would match "", on ?(b|c)
// @(a|?(b|c)|d) => would match "", on ?(b|c)
//
// ? can adopt @, because 0 or 1 is allowed
// ?(a|@(b|c)|d) => ?(a|b|c|d)
//
// ? and @ CANNOT adopt * or +, because >1 would be allowed
// ?(a|*(b|c)|d) => would match bbb on *(b|c)
// @(a|*(b|c)|d) => would match bbb on *(b|c)
// ?(a|+(b|c)|d) => would match bbb on +(b|c)
// @(a|+(b|c)|d) => would match bbb on +(b|c)
//
// ! CANNOT adopt ! (nothing else can either)
// !(a|!(b|c)|d) => !(a|b|c|d) would fail to match on b (not not b|c)
//
// ! can adopt @
// !(a|@(b|c)|d) => !(a|b|c|d)
//
// ! CANNOT adopt *
// !(a|*(b|c)|d) => !(a|b|c|d) would match on bbb, not allowed
//
// ! CANNOT adopt +
// !(a|+(b|c)|d) => !(a|b|c|d) would match on bbb, not allowed
//
// ! CANNOT adopt ?
// x!(a|?(b|c)|d) => x!(a|b|c|d) would fail to match "x"
const adoptionMap = new Map([
['!', ['@']],
['?', ['?', '@']],
['@', ['@']],
['*', ['*', '+', '?', '@']],
['+', ['+', '@']],
]);
// nested extglobs that can be adopted in, but with the addition of
// a blank '' element.
const adoptionWithSpaceMap = new Map([
['!', ['?']],
['@', ['?']],
['+', ['?', '*']],
]);
// union of the previous two maps
const adoptionAnyMap = new Map([
['!', ['?', '@']],
['?', ['?', '@']],
['@', ['?', '@']],
['*', ['*', '+', '?', '@']],
['+', ['+', '@', '?', '*']],
]);
// Extglobs that can take over their parent if they are the only child
// the key is parent, value maps child to resulting extglob parent type
// '@' is omitted because it's a special case. An `@` extglob with a single
// member can always be usurped by that subpattern.
const usurpMap = new Map([
['!', new Map([['!', '@']])],
[
'?',
new Map([
['*', '*'],
['+', '*'],
]),
],
[
'@',
new Map([
['!', '!'],
['?', '?'],
['@', '@'],
['*', '*'],
['+', '+'],
]),
],
[
'+',
new Map([
['?', '*'],
['*', '*'],
]),
],
]);
// Patterns that get prepended to bind to the start of either the
// entire string, or just a single path portion, to prevent dots
// and/or traversal patterns, when needed.
// Exts don't need the ^ or / bit, because the root binds that already.
const startNoTraversal = '(?!(?:^|/)\\.\\.?(?:$|/))';
const startNoDot = '(?!\\.)';
// characters that indicate a start of pattern needs the "no dots" bit,
// because a dot *might* be matched. ( is not in the list, because in
// the case of a child extglob, it will handle the prevention itself.
const addPatternStart = new Set(['[', '.']);
// cases where traversal is A-OK, no dot prevention needed
const justDots = new Set(['..', '.']);
const reSpecials = new Set('().*{}+?[]^$\\!');
const regExpEscape = (s) => s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
// any single thing other than /
const qmark = '[^/]';
// * => any number of characters
const star = qmark + '*?';
// use + when we need to ensure that *something* matches, because the * is
// the only thing in the path portion.
const starNoEmpty = qmark + '+?';
// remove the \ chars that we added if we end up doing a nonmagic compare
// const deslash = (s: string) => s.replace(/\\(.)/g, '$1')
let ID = 0;
class AST {
type;
#root;
#hasMagic;
#uflag = false;
#parts = [];
#parent;
#parentIndex;
#negs;
#filledNegs = false;
#options;
#toString;
// set to true if it's an extglob with no children
// (which really means one child of '')
#emptyExt = false;
id = ++ID;
get depth() {
return (this.#parent?.depth ?? -1) + 1;
}
[Symbol.for('nodejs.util.inspect.custom')]() {
return {
'@@type': 'AST',
id: this.id,
type: this.type,
root: this.#root.id,
parent: this.#parent?.id,
depth: this.depth,
partsLength: this.#parts.length,
parts: this.#parts,
};
}
constructor(type, parent, options = {}) {
this.type = type;
// extglobs are inherently magical
if (type)
this.#hasMagic = true;
this.#parent = parent;
this.#root = this.#parent ? this.#parent.#root : this;
this.#options = this.#root === this ? options : this.#root.#options;
this.#negs = this.#root === this ? [] : this.#root.#negs;
if (type === '!' && !this.#root.#filledNegs)
this.#negs.push(this);
this.#parentIndex = this.#parent ? this.#parent.#parts.length : 0;
}
get hasMagic() {
/* c8 ignore start */
if (this.#hasMagic !== undefined)
return this.#hasMagic;
/* c8 ignore stop */
for (const p of this.#parts) {
if (typeof p === 'string')
continue;
if (p.type || p.hasMagic)
return (this.#hasMagic = true);
}
// note: will be undefined until we generate the regexp src and find out
return this.#hasMagic;
}
// reconstructs the pattern
toString() {
return (this.#toString !== undefined ? this.#toString
: !this.type ?
(this.#toString = this.#parts.map(p => String(p)).join(''))
: (this.#toString =
this.type +
'(' +
this.#parts.map(p => String(p)).join('|') +
')'));
}
#fillNegs() {
/* c8 ignore start */
if (this !== this.#root)
throw new Error('should only call on root');
if (this.#filledNegs)
return this;
/* c8 ignore stop */
// call toString() once to fill this out
this.toString();
this.#filledNegs = true;
let n;
while ((n = this.#negs.pop())) {
if (n.type !== '!')
continue;
// walk up the tree, appending everthing that comes AFTER parentIndex
let p = n;
let pp = p.#parent;
while (pp) {
for (let i = p.#parentIndex + 1; !pp.type && i < pp.#parts.length; i++) {
for (const part of n.#parts) {
/* c8 ignore start */
if (typeof part === 'string') {
throw new Error('string part in extglob AST??');
}
/* c8 ignore stop */
part.copyIn(pp.#parts[i]);
}
}
p = pp;
pp = p.#parent;
}
}
return this;
}
push(...parts) {
for (const p of parts) {
if (p === '')
continue;
/* c8 ignore start */
if (typeof p !== 'string' &&
!(p instanceof _a && p.#parent === this)) {
throw new Error('invalid part: ' + p);
}
/* c8 ignore stop */
this.#parts.push(p);
}
}
toJSON() {
const ret = this.type === null ?
this.#parts
.slice()
.map(p => (typeof p === 'string' ? p : p.toJSON()))
: [this.type, ...this.#parts.map(p => p.toJSON())];
if (this.isStart() && !this.type)
ret.unshift([]);
if (this.isEnd() &&
(this === this.#root ||
(this.#root.#filledNegs && this.#parent?.type === '!'))) {
ret.push({});
}
return ret;
}
isStart() {
if (this.#root === this)
return true;
// if (this.type) return !!this.#parent?.isStart()
if (!this.#parent?.isStart())
return false;
if (this.#parentIndex === 0)
return true;
// if everything AHEAD of this is a negation, then it's still the "start"
const p = this.#parent;
for (let i = 0; i < this.#parentIndex; i++) {
const pp = p.#parts[i];
if (!(pp instanceof _a && pp.type === '!')) {
return false;
}
}
return true;
}
isEnd() {
if (this.#root === this)
return true;
if (this.#parent?.type === '!')
return true;
if (!this.#parent?.isEnd())
return false;
if (!this.type)
return this.#parent?.isEnd();
// if not root, it'll always have a parent
/* c8 ignore start */
const pl = this.#parent ? this.#parent.#parts.length : 0;
/* c8 ignore stop */
return this.#parentIndex === pl - 1;
}
copyIn(part) {
if (typeof part === 'string')
this.push(part);
else
this.push(part.clone(this));
}
clone(parent) {
const c = new _a(this.type, parent);
for (const p of this.#parts) {
c.copyIn(p);
}
return c;
}
static #parseAST(str, ast, pos, opt, extDepth) {
const maxDepth = opt.maxExtglobRecursion ?? 2;
let escaping = false;
let inBrace = false;
let braceStart = -1;
let braceNeg = false;
if (ast.type === null) {
// outside of a extglob, append until we find a start
let i = pos;
let acc = '';
while (i < str.length) {
const c = str.charAt(i++);
// still accumulate escapes at this point, but we do ignore
// starts that are escaped
if (escaping || c === '\\') {
escaping = !escaping;
acc += c;
continue;
}
if (inBrace) {
if (i === braceStart + 1) {
if (c === '^' || c === '!') {
braceNeg = true;
}
}
else if (c === ']' && !(i === braceStart + 2 && braceNeg)) {
inBrace = false;
}
acc += c;
continue;
}
else if (c === '[') {
inBrace = true;
braceStart = i;
braceNeg = false;
acc += c;
continue;
}
// we don't have to check for adoption here, because that's
// done at the other recursion point.
const doRecurse = !opt.noext &&
isExtglobType(c) &&
str.charAt(i) === '(' &&
extDepth <= maxDepth;
if (doRecurse) {
ast.push(acc);
acc = '';
const ext = new _a(c, ast);
i = _a.#parseAST(str, ext, i, opt, extDepth + 1);
ast.push(ext);
continue;
}
acc += c;
}
ast.push(acc);
return i;
}
// some kind of extglob, pos is at the (
// find the next | or )
let i = pos + 1;
let part = new _a(null, ast);
const parts = [];
let acc = '';
while (i < str.length) {
const c = str.charAt(i++);
// still accumulate escapes at this point, but we do ignore
// starts that are escaped
if (escaping || c === '\\') {
escaping = !escaping;
acc += c;
continue;
}
if (inBrace) {
if (i === braceStart + 1) {
if (c === '^' || c === '!') {
braceNeg = true;
}
}
else if (c === ']' && !(i === braceStart + 2 && braceNeg)) {
inBrace = false;
}
acc += c;
continue;
}
else if (c === '[') {
inBrace = true;
braceStart = i;
braceNeg = false;
acc += c;
continue;
}
const doRecurse = !opt.noext &&
isExtglobType(c) &&
str.charAt(i) === '(' &&
/* c8 ignore start - the maxDepth is sufficient here */
(extDepth <= maxDepth || (ast && ast.#canAdoptType(c)));
/* c8 ignore stop */
if (doRecurse) {
const depthAdd = ast && ast.#canAdoptType(c) ? 0 : 1;
part.push(acc);
acc = '';
const ext = new _a(c, part);
part.push(ext);
i = _a.#parseAST(str, ext, i, opt, extDepth + depthAdd);
continue;
}
if (c === '|') {
part.push(acc);
acc = '';
parts.push(part);
part = new _a(null, ast);
continue;
}
if (c === ')') {
if (acc === '' && ast.#parts.length === 0) {
ast.#emptyExt = true;
}
part.push(acc);
acc = '';
ast.push(...parts, part);
return i;
}
acc += c;
}
// unfinished extglob
// if we got here, it was a malformed extglob! not an extglob, but
// maybe something else in there.
ast.type = null;
ast.#hasMagic = undefined;
ast.#parts = [str.substring(pos - 1)];
return i;
}
#canAdoptWithSpace(child) {
return this.#canAdopt(child, adoptionWithSpaceMap);
}
#canAdopt(child, map = adoptionMap) {
if (!child ||
typeof child !== 'object' ||
child.type !== null ||
child.#parts.length !== 1 ||
this.type === null) {
return false;
}
const gc = child.#parts[0];
if (!gc || typeof gc !== 'object' || gc.type === null) {
return false;
}
return this.#canAdoptType(gc.type, map);
}
#canAdoptType(c, map = adoptionAnyMap) {
return !!map.get(this.type)?.includes(c);
}
#adoptWithSpace(child, index) {
const gc = child.#parts[0];
const blank = new _a(null, gc, this.options);
blank.#parts.push('');
gc.push(blank);
this.#adopt(child, index);
}
#adopt(child, index) {
const gc = child.#parts[0];
this.#parts.splice(index, 1, ...gc.#parts);
for (const p of gc.#parts) {
if (typeof p === 'object')
p.#parent = this;
}
this.#toString = undefined;
}
#canUsurpType(c) {
const m = usurpMap.get(this.type);
return !!m?.has(c);
}
#canUsurp(child) {
if (!child ||
typeof child !== 'object' ||
child.type !== null ||
child.#parts.length !== 1 ||
this.type === null ||
this.#parts.length !== 1) {
return false;
}
const gc = child.#parts[0];
if (!gc || typeof gc !== 'object' || gc.type === null) {
return false;
}
return this.#canUsurpType(gc.type);
}
#usurp(child) {
const m = usurpMap.get(this.type);
const gc = child.#parts[0];
const nt = m?.get(gc.type);
/* c8 ignore start - impossible */
if (!nt)
return false;
/* c8 ignore stop */
this.#parts = gc.#parts;
for (const p of this.#parts) {
if (typeof p === 'object') {
p.#parent = this;
}
}
this.type = nt;
this.#toString = undefined;
this.#emptyExt = false;
}
static fromGlob(pattern, options = {}) {
const ast = new _a(null, undefined, options);
_a.#parseAST(pattern, ast, 0, options, 0);
return ast;
}
// returns the regular expression if there's magic, or the unescaped
// string if not.
toMMPattern() {
// should only be called on root
/* c8 ignore start */
if (this !== this.#root)
return this.#root.toMMPattern();
/* c8 ignore stop */
const glob = this.toString();
const [re, body, hasMagic, uflag] = this.toRegExpSource();
// if we're in nocase mode, and not nocaseMagicOnly, then we do
// still need a regular expression if we have to case-insensitively
// match capital/lowercase characters.
const anyMagic = hasMagic ||
this.#hasMagic ||
(this.#options.nocase &&
!this.#options.nocaseMagicOnly &&
glob.toUpperCase() !== glob.toLowerCase());
if (!anyMagic) {
return body;
}
const flags = (this.#options.nocase ? 'i' : '') + (uflag ? 'u' : '');
return Object.assign(new RegExp(`^${re}$`, flags), {
_src: re,
_glob: glob,
});
}
get options() {
return this.#options;
}
// returns the string match, the regexp source, whether there's magic
// in the regexp (so a regular expression is required) and whether or
// not the uflag is needed for the regular expression (for posix classes)
// TODO: instead of injecting the start/end at this point, just return
// the BODY of the regexp, along with the start/end portions suitable
// for binding the start/end in either a joined full-path makeRe context
// (where we bind to (^|/), or a standalone matchPart context (where
// we bind to ^, and not /). Otherwise slashes get duped!
//
// In part-matching mode, the start is:
// - if not isStart: nothing
// - if traversal possible, but not allowed: ^(?!\.\.?$)
// - if dots allowed or not possible: ^
// - if dots possible and not allowed: ^(?!\.)
// end is:
// - if not isEnd(): nothing
// - else: $
//
// In full-path matching mode, we put the slash at the START of the
// pattern, so start is:
// - if first pattern: same as part-matching mode
// - if not isStart(): nothing
// - if traversal possible, but not allowed: /(?!\.\.?(?:$|/))
// - if dots allowed or not possible: /
// - if dots possible and not allowed: /(?!\.)
// end is:
// - if last pattern, same as part-matching mode
// - else nothing
//
// Always put the (?:$|/) on negated tails, though, because that has to be
// there to bind the end of the negated pattern portion, and it's easier to
// just stick it in now rather than try to inject it later in the middle of
// the pattern.
//
// We can just always return the same end, and leave it up to the caller
// to know whether it's going to be used joined or in parts.
// And, if the start is adjusted slightly, can do the same there:
// - if not isStart: nothing
// - if traversal possible, but not allowed: (?:/|^)(?!\.\.?$)
// - if dots allowed or not possible: (?:/|^)
// - if dots possible and not allowed: (?:/|^)(?!\.)
//
// But it's better to have a simpler binding without a conditional, for
// performance, so probably better to return both start options.
//
// Then the caller just ignores the end if it's not the first pattern,
// and the start always gets applied.
//
// But that's always going to be $ if it's the ending pattern, or nothing,
// so the caller can just attach $ at the end of the pattern when building.
//
// So the todo is:
// - better detect what kind of start is needed
// - return both flavors of starting pattern
// - attach $ at the end of the pattern when creating the actual RegExp
//
// Ah, but wait, no, that all only applies to the root when the first pattern
// is not an extglob. If the first pattern IS an extglob, then we need all
// that dot prevention biz to live in the extglob portions, because eg
// +(*|.x*) can match .xy but not .yx.
//
// So, return the two flavors if it's #root and the first child is not an
// AST, otherwise leave it to the child AST to handle it, and there,
// use the (?:^|/) style of start binding.
//
// Even simplified further:
// - Since the start for a join is eg /(?!\.) and the start for a part
// is ^(?!\.), we can just prepend (?!\.) to the pattern (either root
// or start or whatever) and prepend ^ or / at the Regexp construction.
toRegExpSource(allowDot) {
const dot = allowDot ?? !!this.#options.dot;
if (this.#root === this) {
this.#flatten();
this.#fillNegs();
}
if (!isExtglobAST(this)) {
const noEmpty = this.isStart() &&
this.isEnd() &&
!this.#parts.some(s => typeof s !== 'string');
const src = this.#parts
.map(p => {
const [re, _, hasMagic, uflag] = typeof p === 'string' ?
_a.#parseGlob(p, this.#hasMagic, noEmpty)
: p.toRegExpSource(allowDot);
this.#hasMagic = this.#hasMagic || hasMagic;
this.#uflag = this.#uflag || uflag;
return re;
})
.join('');
let start = '';
if (this.isStart()) {
if (typeof this.#parts[0] === 'string') {
// this is the string that will match the start of the pattern,
// so we need to protect against dots and such.
// '.' and '..' cannot match unless the pattern is that exactly,
// even if it starts with . or dot:true is set.
const dotTravAllowed = this.#parts.length === 1 && justDots.has(this.#parts[0]);
if (!dotTravAllowed) {
const aps = addPatternStart;
// check if we have a possibility of matching . or ..,
// and prevent that.
const needNoTrav =
// dots are allowed, and the pattern starts with [ or .
(dot && aps.has(src.charAt(0))) ||
// the pattern starts with \., and then [ or .
(src.startsWith('\\.') && aps.has(src.charAt(2))) ||
// the pattern starts with \.\., and then [ or .
(src.startsWith('\\.\\.') && aps.has(src.charAt(4)));
// no need to prevent dots if it can't match a dot, or if a
// sub-pattern will be preventing it anyway.
const needNoDot = !dot && !allowDot && aps.has(src.charAt(0));
start =
needNoTrav ? startNoTraversal
: needNoDot ? startNoDot
: '';
}
}
}
// append the "end of path portion" pattern to negation tails
let end = '';
if (this.isEnd() &&
this.#root.#filledNegs &&
this.#parent?.type === '!') {
end = '(?:$|\\/)';
}
const final = start + src + end;
return [
final,
(0, unescape_js_1.unescape)(src),
(this.#hasMagic = !!this.#hasMagic),
this.#uflag,
];
}
// We need to calculate the body *twice* if it's a repeat pattern
// at the start, once in nodot mode, then again in dot mode, so a
// pattern like *(?) can match 'x.y'
const repeated = this.type === '*' || this.type === '+';
// some kind of extglob
const start = this.type === '!' ? '(?:(?!(?:' : '(?:';
let body = this.#partsToRegExp(dot);
if (this.isStart() && this.isEnd() && !body && this.type !== '!') {
// invalid extglob, has to at least be *something* present, if it's
// the entire path portion.
const s = this.toString();
const me = this;
me.#parts = [s];
me.type = null;
me.#hasMagic = undefined;
return [s, (0, unescape_js_1.unescape)(this.toString()), false, false];
}
let bodyDotAllowed = !repeated || allowDot || dot || !startNoDot ?
''
: this.#partsToRegExp(true);
if (bodyDotAllowed === body) {
bodyDotAllowed = '';
}
if (bodyDotAllowed) {
body = `(?:${body})(?:${bodyDotAllowed})*?`;
}
// an empty !() is exactly equivalent to a starNoEmpty
let final = '';
if (this.type === '!' && this.#emptyExt) {
final = (this.isStart() && !dot ? startNoDot : '') + starNoEmpty;
}
else {
const close = this.type === '!' ?
// !() must match something,but !(x) can match ''
'))' +
(this.isStart() && !dot && !allowDot ? startNoDot : '') +
star +
')'
: this.type === '@' ? ')'
: this.type === '?' ? ')?'
: this.type === '+' && bodyDotAllowed ? ')'
: this.type === '*' && bodyDotAllowed ? `)?`
: `)${this.type}`;
final = start + body + close;
}
return [
final,
(0, unescape_js_1.unescape)(body),
(this.#hasMagic = !!this.#hasMagic),
this.#uflag,
];
}
#flatten() {
if (!isExtglobAST(this)) {
for (const p of this.#parts) {
if (typeof p === 'object') {
p.#flatten();
}
}
}
else {
// do up to 10 passes to flatten as much as possible
let iterations = 0;
let done = false;
do {
done = true;
for (let i = 0; i < this.#parts.length; i++) {
const c = this.#parts[i];
if (typeof c === 'object') {
c.#flatten();
if (this.#canAdopt(c)) {
done = false;
this.#adopt(c, i);
}
else if (this.#canAdoptWithSpace(c)) {
done = false;
this.#adoptWithSpace(c, i);
}
else if (this.#canUsurp(c)) {
done = false;
this.#usurp(c);
}
}
}
} while (!done && ++iterations < 10);
}
this.#toString = undefined;
}
#partsToRegExp(dot) {
return this.#parts
.map(p => {
// extglob ASTs should only contain parent ASTs
/* c8 ignore start */
if (typeof p === 'string') {
throw new Error('string type in extglob ast??');
}
/* c8 ignore stop */
// can ignore hasMagic, because extglobs are already always magic
const [re, _, _hasMagic, uflag] = p.toRegExpSource(dot);
this.#uflag = this.#uflag || uflag;
return re;
})
.filter(p => !(this.isStart() && this.isEnd()) || !!p)
.join('|');
}
static #parseGlob(glob, hasMagic, noEmpty = false) {
let escaping = false;
let re = '';
let uflag = false;
// multiple stars that aren't globstars coalesce into one *
let inStar = false;
for (let i = 0; i < glob.length; i++) {
const c = glob.charAt(i);
if (escaping) {
escaping = false;
re += (reSpecials.has(c) ? '\\' : '') + c;
continue;
}
if (c === '*') {
if (inStar)
continue;
inStar = true;
re += noEmpty && /^[*]+$/.test(glob) ? starNoEmpty : star;
hasMagic = true;
continue;
}
else {
inStar = false;
}
if (c === '\\') {
if (i === glob.length - 1) {
re += '\\\\';
}
else {
escaping = true;
}
continue;
}
if (c === '[') {
const [src, needUflag, consumed, magic] = (0, brace_expressions_js_1.parseClass)(glob, i);
if (consumed) {
re += src;
uflag = uflag || needUflag;
i += consumed - 1;
hasMagic = hasMagic || magic;
continue;
}
}
if (c === '?') {
re += qmark;
hasMagic = true;
continue;
}
re += regExpEscape(c);
}
return [re, (0, unescape_js_1.unescape)(glob), !!hasMagic, uflag];
}
}
exports.AST = AST;
_a = AST;
//# sourceMappingURL=ast.js.map

View File

@@ -0,0 +1,140 @@
'use strict';
module.exports = function generate_items(it, $keyword, $ruleType) {
var out = ' ';
var $lvl = it.level;
var $dataLvl = it.dataLevel;
var $schema = it.schema[$keyword];
var $schemaPath = it.schemaPath + it.util.getProperty($keyword);
var $errSchemaPath = it.errSchemaPath + '/' + $keyword;
var $breakOnError = !it.opts.allErrors;
var $data = 'data' + ($dataLvl || '');
var $valid = 'valid' + $lvl;
var $errs = 'errs__' + $lvl;
var $it = it.util.copy(it);
var $closingBraces = '';
$it.level++;
var $nextValid = 'valid' + $it.level;
var $idx = 'i' + $lvl,
$dataNxt = $it.dataLevel = it.dataLevel + 1,
$nextData = 'data' + $dataNxt,
$currentBaseId = it.baseId;
out += 'var ' + ($errs) + ' = errors;var ' + ($valid) + ';';
if (Array.isArray($schema)) {
var $additionalItems = it.schema.additionalItems;
if ($additionalItems === false) {
out += ' ' + ($valid) + ' = ' + ($data) + '.length <= ' + ($schema.length) + '; ';
var $currErrSchemaPath = $errSchemaPath;
$errSchemaPath = it.errSchemaPath + '/additionalItems';
out += ' if (!' + ($valid) + ') { ';
var $$outStack = $$outStack || [];
$$outStack.push(out);
out = ''; /* istanbul ignore else */
if (it.createErrors !== false) {
out += ' { keyword: \'' + ('additionalItems') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { limit: ' + ($schema.length) + ' } ';
if (it.opts.messages !== false) {
out += ' , message: \'should NOT have more than ' + ($schema.length) + ' items\' ';
}
if (it.opts.verbose) {
out += ' , schema: false , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' ';
}
out += ' } ';
} else {
out += ' {} ';
}
var __err = out;
out = $$outStack.pop();
if (!it.compositeRule && $breakOnError) {
/* istanbul ignore if */
if (it.async) {
out += ' throw new ValidationError([' + (__err) + ']); ';
} else {
out += ' validate.errors = [' + (__err) + ']; return false; ';
}
} else {
out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; ';
}
out += ' } ';
$errSchemaPath = $currErrSchemaPath;
if ($breakOnError) {
$closingBraces += '}';
out += ' else { ';
}
}
var arr1 = $schema;
if (arr1) {
var $sch, $i = -1,
l1 = arr1.length - 1;
while ($i < l1) {
$sch = arr1[$i += 1];
if ((it.opts.strictKeywords ? (typeof $sch == 'object' && Object.keys($sch).length > 0) || $sch === false : it.util.schemaHasRules($sch, it.RULES.all))) {
out += ' ' + ($nextValid) + ' = true; if (' + ($data) + '.length > ' + ($i) + ') { ';
var $passData = $data + '[' + $i + ']';
$it.schema = $sch;
$it.schemaPath = $schemaPath + '[' + $i + ']';
$it.errSchemaPath = $errSchemaPath + '/' + $i;
$it.errorPath = it.util.getPathExpr(it.errorPath, $i, it.opts.jsonPointers, true);
$it.dataPathArr[$dataNxt] = $i;
var $code = it.validate($it);
$it.baseId = $currentBaseId;
if (it.util.varOccurences($code, $nextData) < 2) {
out += ' ' + (it.util.varReplace($code, $nextData, $passData)) + ' ';
} else {
out += ' var ' + ($nextData) + ' = ' + ($passData) + '; ' + ($code) + ' ';
}
out += ' } ';
if ($breakOnError) {
out += ' if (' + ($nextValid) + ') { ';
$closingBraces += '}';
}
}
}
}
if (typeof $additionalItems == 'object' && (it.opts.strictKeywords ? (typeof $additionalItems == 'object' && Object.keys($additionalItems).length > 0) || $additionalItems === false : it.util.schemaHasRules($additionalItems, it.RULES.all))) {
$it.schema = $additionalItems;
$it.schemaPath = it.schemaPath + '.additionalItems';
$it.errSchemaPath = it.errSchemaPath + '/additionalItems';
out += ' ' + ($nextValid) + ' = true; if (' + ($data) + '.length > ' + ($schema.length) + ') { for (var ' + ($idx) + ' = ' + ($schema.length) + '; ' + ($idx) + ' < ' + ($data) + '.length; ' + ($idx) + '++) { ';
$it.errorPath = it.util.getPathExpr(it.errorPath, $idx, it.opts.jsonPointers, true);
var $passData = $data + '[' + $idx + ']';
$it.dataPathArr[$dataNxt] = $idx;
var $code = it.validate($it);
$it.baseId = $currentBaseId;
if (it.util.varOccurences($code, $nextData) < 2) {
out += ' ' + (it.util.varReplace($code, $nextData, $passData)) + ' ';
} else {
out += ' var ' + ($nextData) + ' = ' + ($passData) + '; ' + ($code) + ' ';
}
if ($breakOnError) {
out += ' if (!' + ($nextValid) + ') break; ';
}
out += ' } } ';
if ($breakOnError) {
out += ' if (' + ($nextValid) + ') { ';
$closingBraces += '}';
}
}
} else if ((it.opts.strictKeywords ? (typeof $schema == 'object' && Object.keys($schema).length > 0) || $schema === false : it.util.schemaHasRules($schema, it.RULES.all))) {
$it.schema = $schema;
$it.schemaPath = $schemaPath;
$it.errSchemaPath = $errSchemaPath;
out += ' for (var ' + ($idx) + ' = ' + (0) + '; ' + ($idx) + ' < ' + ($data) + '.length; ' + ($idx) + '++) { ';
$it.errorPath = it.util.getPathExpr(it.errorPath, $idx, it.opts.jsonPointers, true);
var $passData = $data + '[' + $idx + ']';
$it.dataPathArr[$dataNxt] = $idx;
var $code = it.validate($it);
$it.baseId = $currentBaseId;
if (it.util.varOccurences($code, $nextData) < 2) {
out += ' ' + (it.util.varReplace($code, $nextData, $passData)) + ' ';
} else {
out += ' var ' + ($nextData) + ' = ' + ($passData) + '; ' + ($code) + ' ';
}
if ($breakOnError) {
out += ' if (!' + ($nextValid) + ') break; ';
}
out += ' }';
}
if ($breakOnError) {
out += ' ' + ($closingBraces) + ' if (' + ($errs) + ' == errors) {';
}
return out;
}

View File

@@ -0,0 +1,4 @@
function _nonIterableSpread() {
throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
module.exports = _nonIterableSpread, module.exports.__esModule = true, module.exports["default"] = module.exports;

View File

@@ -0,0 +1,10 @@
import parse from './parse.js';
import { unsafeStringify } from './stringify.js';
export default function v6ToV1(uuid) {
const v6Bytes = typeof uuid === 'string' ? parse(uuid) : uuid;
const v1Bytes = _v6ToV1(v6Bytes);
return typeof uuid === 'string' ? unsafeStringify(v1Bytes) : v1Bytes;
}
function _v6ToV1(v6Bytes) {
return Uint8Array.of(((v6Bytes[3] & 0x0f) << 4) | ((v6Bytes[4] >> 4) & 0x0f), ((v6Bytes[4] & 0x0f) << 4) | ((v6Bytes[5] & 0xf0) >> 4), ((v6Bytes[5] & 0x0f) << 4) | (v6Bytes[6] & 0x0f), v6Bytes[7], ((v6Bytes[1] & 0x0f) << 4) | ((v6Bytes[2] & 0xf0) >> 4), ((v6Bytes[2] & 0x0f) << 4) | ((v6Bytes[3] & 0xf0) >> 4), 0x10 | ((v6Bytes[0] & 0xf0) >> 4), ((v6Bytes[0] & 0x0f) << 4) | ((v6Bytes[1] & 0xf0) >> 4), v6Bytes[8], v6Bytes[9], v6Bytes[10], v6Bytes[11], v6Bytes[12], v6Bytes[13], v6Bytes[14], v6Bytes[15]);
}

View File

@@ -0,0 +1,8 @@
language: node_js
node_js:
- "4"
- "6"
- "7"
- "8"
after_script:
- coveralls < coverage/lcov.info

View File

@@ -0,0 +1,216 @@
/**
* @fileoverview Enforce return after a callback.
* @author Jamund Ferguson
* @deprecated in ESLint v7.0.0
*/
"use strict";
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../types').Rule.RuleModule} */
module.exports = {
meta: {
deprecated: {
message: "Node.js rules were moved out of ESLint core.",
url: "https://eslint.org/docs/latest/use/migrating-to-7.0.0#deprecate-node-rules",
deprecatedSince: "7.0.0",
availableUntil: "11.0.0",
replacedBy: [
{
message:
"eslint-plugin-n now maintains deprecated Node.js-related rules.",
plugin: {
name: "eslint-plugin-n",
url: "https://github.com/eslint-community/eslint-plugin-n",
},
rule: {
name: "callback-return",
url: "https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/callback-return.md",
},
},
],
},
type: "suggestion",
docs: {
description: "Require `return` statements after callbacks",
recommended: false,
url: "https://eslint.org/docs/latest/rules/callback-return",
},
schema: [
{
type: "array",
items: { type: "string" },
},
],
messages: {
missingReturn: "Expected return with your callback function.",
},
},
create(context) {
const callbacks = context.options[0] || ["callback", "cb", "next"],
sourceCode = context.sourceCode;
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
/**
* Find the closest parent matching a list of types.
* @param {ASTNode} node The node whose parents we are searching
* @param {Array} types The node types to match
* @returns {ASTNode} The matched node or undefined.
*/
function findClosestParentOfType(node, types) {
if (!node.parent) {
return null;
}
if (!types.includes(node.parent.type)) {
return findClosestParentOfType(node.parent, types);
}
return node.parent;
}
/**
* Check to see if a node contains only identifiers
* @param {ASTNode} node The node to check
* @returns {boolean} Whether or not the node contains only identifiers
*/
function containsOnlyIdentifiers(node) {
if (node.type === "Identifier") {
return true;
}
if (node.type === "MemberExpression") {
if (node.object.type === "Identifier") {
return true;
}
if (node.object.type === "MemberExpression") {
return containsOnlyIdentifiers(node.object);
}
}
return false;
}
/**
* Check to see if a CallExpression is in our callback list.
* @param {ASTNode} node The node to check against our callback names list.
* @returns {boolean} Whether or not this function matches our callback name.
*/
function isCallback(node) {
return (
containsOnlyIdentifiers(node.callee) &&
callbacks.includes(sourceCode.getText(node.callee))
);
}
/**
* Determines whether or not the callback is part of a callback expression.
* @param {ASTNode} node The callback node
* @param {ASTNode} parentNode The expression node
* @returns {boolean} Whether or not this is part of a callback expression
*/
function isCallbackExpression(node, parentNode) {
// ensure the parent node exists and is an expression
if (!parentNode || parentNode.type !== "ExpressionStatement") {
return false;
}
// cb()
if (parentNode.expression === node) {
return true;
}
// special case for cb && cb() and similar
if (
parentNode.expression.type === "BinaryExpression" ||
parentNode.expression.type === "LogicalExpression"
) {
if (parentNode.expression.right === node) {
return true;
}
}
return false;
}
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
return {
CallExpression(node) {
// if we're not a callback we can return
if (!isCallback(node)) {
return;
}
// find the closest block, return or loop
const closestBlock =
findClosestParentOfType(node, [
"BlockStatement",
"ReturnStatement",
"ArrowFunctionExpression",
]) || {};
// if our parent is a return we know we're ok
if (closestBlock.type === "ReturnStatement") {
return;
}
// arrow functions don't always have blocks and implicitly return
if (closestBlock.type === "ArrowFunctionExpression") {
return;
}
// block statements are part of functions and most if statements
if (closestBlock.type === "BlockStatement") {
// find the last item in the block
const lastItem = closestBlock.body.at(-1);
// if the callback is the last thing in a block that might be ok
if (isCallbackExpression(node, lastItem)) {
const parentType = closestBlock.parent.type;
// but only if the block is part of a function
if (
parentType === "FunctionExpression" ||
parentType === "FunctionDeclaration" ||
parentType === "ArrowFunctionExpression"
) {
return;
}
}
// ending a block with a return is also ok
if (lastItem.type === "ReturnStatement") {
// but only if the callback is immediately before
if (
isCallbackExpression(node, closestBlock.body.at(-2))
) {
return;
}
}
}
// as long as you're the child of a function at this point you should be asked to return
if (
findClosestParentOfType(node, [
"FunctionDeclaration",
"FunctionExpression",
"ArrowFunctionExpression",
])
) {
context.report({ node, messageId: "missingReturn" });
}
},
};
},
};

View File

@@ -0,0 +1,68 @@
'use strict';
const path = require('path');
const fs = require('fs');
const {promisify} = require('util');
const pLocate = require('p-locate');
const fsStat = promisify(fs.stat);
const fsLStat = promisify(fs.lstat);
const typeMappings = {
directory: 'isDirectory',
file: 'isFile'
};
function checkType({type}) {
if (type in typeMappings) {
return;
}
throw new Error(`Invalid type specified: ${type}`);
}
const matchType = (type, stat) => type === undefined || stat[typeMappings[type]]();
module.exports = async (paths, options) => {
options = {
cwd: process.cwd(),
type: 'file',
allowSymlinks: true,
...options
};
checkType(options);
const statFn = options.allowSymlinks ? fsStat : fsLStat;
return pLocate(paths, async path_ => {
try {
const stat = await statFn(path.resolve(options.cwd, path_));
return matchType(options.type, stat);
} catch {
return false;
}
}, options);
};
module.exports.sync = (paths, options) => {
options = {
cwd: process.cwd(),
allowSymlinks: true,
type: 'file',
...options
};
checkType(options);
const statFn = options.allowSymlinks ? fs.statSync : fs.lstatSync;
for (const path_ of paths) {
try {
const stat = statFn(path.resolve(options.cwd, path_));
if (matchType(options.type, stat)) {
return path_;
}
} catch {}
}
};

View File

@@ -0,0 +1,16 @@
/**
* Get visitor keys of a given node.
* @param {object} node The AST node to get keys.
* @returns {readonly string[]} Visitor keys of the node.
*/
export function getKeys(node: object): readonly string[];
/**
* Make the union set with `KEYS` and given keys.
* @param {VisitorKeys} additionalKeys The additional keys.
* @returns {VisitorKeys} The union set.
*/
export function unionWith(additionalKeys: VisitorKeys): VisitorKeys;
export { KEYS };
export type VisitorKeys = import('./visitor-keys.js').VisitorKeys;
import KEYS from "./visitor-keys.js";
//# sourceMappingURL=index.d.ts.map

View File

@@ -0,0 +1,105 @@
# ⚡️ Lightning CSS
An extremely fast CSS parser, transformer, and minifier written in Rust. Use it with [Parcel](https://parceljs.org), as a standalone library or CLI, or via a plugin with any other tool.
<img width="680" alt="performance and build size charts" src="https://user-images.githubusercontent.com/19409/189022599-28246659-f94a-46a4-9de0-b6d17adb0e22.png#gh-light-mode-only">
<img width="680" alt="performance and build size charts" src="https://user-images.githubusercontent.com/19409/189022693-6956b044-422b-4f56-9628-d59c6f791095.png#gh-dark-mode-only">
## Features
- **Extremely fast** Parsing and minifying large files is completed in milliseconds, often with significantly smaller output than other tools. See [benchmarks](#benchmarks) below.
- **Typed property values** many other CSS parsers treat property values as an untyped series of tokens. This means that each transformer that wants to do something with these values must interpret them itself, leading to duplicate work and inconsistencies. Lightning CSS parses all values using the grammar from the CSS specification, and exposes a specific value type for each property.
- **Browser-grade parser** Lightning CSS is built on the [cssparser](https://github.com/servo/rust-cssparser) and [selectors](https://github.com/servo/stylo/tree/main/selectors) crates created by Mozilla and used by Firefox and Servo. These provide a solid general purpose CSS-parsing foundation on top of which Lightning CSS implements support for all specific CSS rules and properties.
- **Minification** One of the main purposes of Lightning CSS is to minify CSS to make it smaller. This includes many optimizations including:
- Combining longhand properties into shorthands where possible.
- Merging adjacent rules with the same selectors or declarations when it is safe to do so.
- Combining CSS transforms into a single matrix or vice versa when smaller.
- Removing vendor prefixes that are not needed, based on the provided browser targets.
- Reducing `calc()` expressions where possible.
- Converting colors to shorter hex notation where possible.
- Minifying gradients.
- Minifying CSS grid templates.
- Normalizing property value order.
- Removing default property sub-values which will be inferred by browsers.
- Many micro-optimizations, e.g. converting to shorter units, removing unnecessary quotation marks, etc.
- **Vendor prefixing** Lightning CSS accepts a list of browser targets, and automatically adds (and removes) vendor prefixes.
- **Browserslist configuration** Lightning CSS supports opt-in browserslist configuration discovery to resolve browser targets and integrate with your existing tools and config setup.
- **Syntax lowering** Lightning CSS parses modern CSS syntax, and generates more compatible output where needed, based on browser targets.
- CSS Nesting
- Custom media queries (draft spec)
- Logical properties
* [Color Level 5](https://drafts.csswg.org/css-color-5/)
- `color-mix()` function
- Relative color syntax, e.g. `lab(from purple calc(l * .8) a b)`
- [Color Level 4](https://drafts.csswg.org/css-color-4/)
- `lab()`, `lch()`, `oklab()`, and `oklch()` colors
- `color()` function supporting predefined color spaces such as `display-p3` and `xyz`
- Space separated components in `rgb` and `hsl` functions
- Hex with alpha syntax
- `hwb()` color syntax
- Percent syntax for opacity
- `#rgba` and `#rrggbbaa` hex colors
- Selectors
- `:not` with multiple arguments
- `:lang` with multiple arguments
- `:dir`
- `:is`
- Double position gradient stops (e.g. `red 40% 80%`)
- `clamp()`, `round()`, `rem()`, and `mod()` math functions
- Alignment shorthands (e.g. `place-items`)
- Two-value `overflow` shorthand
- Media query range syntax (e.g. `@media (width <= 100px)` or `@media (100px < width < 500px)`)
- Multi-value `display` property (e.g. `inline flex`)
- `system-ui` font family fallbacks
- **CSS modules** Lightning CSS supports compiling a subset of [CSS modules](https://github.com/css-modules/css-modules) features.
- Locally scoped class and id selectors
- Locally scoped custom identifiers, e.g. `@keyframes` names, grid lines/areas, `@counter-style` names, etc.
- Opt-in support for locally scoped CSS variables and other dashed identifiers.
- `:local()` and `:global()` selectors
- The `composes` property
- **Custom transforms** The Lightning CSS visitor API can be used to implement custom transform plugins.
## Documentation
Lightning CSS can be used from [Parcel](https://parceljs.org), as a standalone library from JavaScript or Rust, using a standalone CLI, or wrapped as a plugin within any other tool. See the [Lightning CSS website](https://lightningcss.dev/docs.html) for documentation.
## Benchmarks
<img width="680" alt="performance and build size charts" src="https://user-images.githubusercontent.com/19409/189022599-28246659-f94a-46a4-9de0-b6d17adb0e22.png#gh-light-mode-only">
<img width="680" alt="performance and build size charts" src="https://user-images.githubusercontent.com/19409/189022693-6956b044-422b-4f56-9628-d59c6f791095.png#gh-dark-mode-only">
```
$ node bench.js bootstrap-4.css
cssnano: 544.809ms
159636 bytes
esbuild: 17.199ms
160332 bytes
lightningcss: 4.16ms
143091 bytes
$ node bench.js animate.css
cssnano: 283.105ms
71723 bytes
esbuild: 11.858ms
72183 bytes
lightningcss: 1.973ms
23666 bytes
$ node bench.js tailwind.css
cssnano: 2.198s
1925626 bytes
esbuild: 107.668ms
1961642 bytes
lightningcss: 43.368ms
1824130 bytes
```
For more benchmarks comparing more tools and input, see [here](http://goalsmashers.github.io/css-minification-benchmark/). Note that some of the tools shown perform unsafe optimizations that may change the behavior of the original CSS in favor of smaller file size. Lightning CSS does not do this the output CSS should always behave identically to the input. Keep this in mind when comparing file sizes between tools.

View File

@@ -0,0 +1,105 @@
/**
* @fileoverview Common error classes
* @author Nicholas C. Zakas
*/
/**
* Error thrown when a file or directory is not found.
*/
export class NotFoundError extends Error {
/**
* Name of the error class.
* @type {string}
*/
name = "NotFoundError";
/**
* Error code.
* @type {string}
*/
code = "ENOENT";
/**
* Creates a new instance.
* @param {string} message The error message.
*/
constructor(message) {
super(`ENOENT: No such file or directory, ${message}`);
}
}
/**
* Error thrown when an operation is not permitted.
*/
export class PermissionError extends Error {
/**
* Name of the error class.
* @type {string}
*/
name = "PermissionError";
/**
* Error code.
* @type {string}
*/
code = "EPERM";
/**
* Creates a new instance.
* @param {string} message The error message.
*/
constructor(message) {
super(`EPERM: Operation not permitted, ${message}`);
}
}
/**
* Error thrown when an operation is not allowed on a directory.
*/
export class DirectoryError extends Error {
/**
* Name of the error class.
* @type {string}
*/
name = "DirectoryError";
/**
* Error code.
* @type {string}
*/
code = "EISDIR";
/**
* Creates a new instance.
* @param {string} message The error message.
*/
constructor(message) {
super(`EISDIR: Illegal operation on a directory, ${message}`);
}
}
/**
* Error thrown when a directory is not empty.
*/
export class NotEmptyError extends Error {
/**
* Name of the error class.
* @type {string}
*/
name = "NotEmptyError";
/**
* Error code.
* @type {string}
*/
code = "ENOTEMPTY";
/**
* Creates a new instance.
* @param {string} message The error message.
*/
constructor(message) {
super(`ENOTEMPTY: Directory not empty, ${message}`);
}
}

View File

@@ -0,0 +1,20 @@
'use strict';
// https://mathiasbynens.be/notes/javascript-encoding
// https://github.com/bestiejs/punycode.js - punycode.ucs2.decode
module.exports = function ucs2length(str) {
var length = 0
, len = str.length
, pos = 0
, value;
while (pos < len) {
length++;
value = str.charCodeAt(pos++);
if (value >= 0xD800 && value <= 0xDBFF && pos < len) {
// high surrogate, and there is a next character
value = str.charCodeAt(pos);
if ((value & 0xFC00) == 0xDC00) pos++; // low surrogate
}
}
return length;
};