WIP: bootstrap and partial real Solana watcher implementation
This commit is contained in:
@@ -0,0 +1,362 @@
|
||||
/**
|
||||
* @fileoverview Rule to require or disallow yoda comparisons
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
const astUtils = require("./utils/ast-utils");
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Determines whether an operator is a comparison operator.
|
||||
* @param {string} operator The operator to check.
|
||||
* @returns {boolean} Whether or not it is a comparison operator.
|
||||
*/
|
||||
function isComparisonOperator(operator) {
|
||||
return /^(?:==|===|!=|!==|<|>|<=|>=)$/u.test(operator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether an operator is an equality operator.
|
||||
* @param {string} operator The operator to check.
|
||||
* @returns {boolean} Whether or not it is an equality operator.
|
||||
*/
|
||||
function isEqualityOperator(operator) {
|
||||
return /^(?:==|===)$/u.test(operator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether an operator is one used in a range test.
|
||||
* Allowed operators are `<` and `<=`.
|
||||
* @param {string} operator The operator to check.
|
||||
* @returns {boolean} Whether the operator is used in range tests.
|
||||
*/
|
||||
function isRangeTestOperator(operator) {
|
||||
return ["<", "<="].includes(operator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a non-Literal node is a negative number that should be
|
||||
* treated as if it were a single Literal node.
|
||||
* @param {ASTNode} node Node to test.
|
||||
* @returns {boolean} True if the node is a negative number that looks like a
|
||||
* real literal and should be treated as such.
|
||||
*/
|
||||
function isNegativeNumericLiteral(node) {
|
||||
return (
|
||||
node.type === "UnaryExpression" &&
|
||||
node.operator === "-" &&
|
||||
node.prefix &&
|
||||
astUtils.isNumericLiteral(node.argument)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a non-Literal node should be treated as a single Literal node.
|
||||
* @param {ASTNode} node Node to test
|
||||
* @returns {boolean} True if the node should be treated as a single Literal node.
|
||||
*/
|
||||
function looksLikeLiteral(node) {
|
||||
return (
|
||||
isNegativeNumericLiteral(node) || astUtils.isStaticTemplateLiteral(node)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to derive a Literal node from nodes that are treated like literals.
|
||||
* @param {ASTNode} node Node to normalize.
|
||||
* @returns {ASTNode} One of the following options.
|
||||
* 1. The original node if the node is already a Literal
|
||||
* 2. A normalized Literal node with the negative number as the value if the
|
||||
* node represents a negative number literal.
|
||||
* 3. A normalized Literal node with the string as the value if the node is
|
||||
* a Template Literal without expression.
|
||||
* 4. Otherwise `null`.
|
||||
*/
|
||||
function getNormalizedLiteral(node) {
|
||||
if (node.type === "Literal") {
|
||||
return node;
|
||||
}
|
||||
|
||||
if (isNegativeNumericLiteral(node)) {
|
||||
return {
|
||||
type: "Literal",
|
||||
value: -node.argument.value,
|
||||
raw: `-${node.argument.value}`,
|
||||
};
|
||||
}
|
||||
|
||||
if (astUtils.isStaticTemplateLiteral(node)) {
|
||||
return {
|
||||
type: "Literal",
|
||||
value: node.quasis[0].value.cooked,
|
||||
raw: node.quasis[0].value.raw,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @type {import('../types').Rule.RuleModule} */
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: "suggestion",
|
||||
|
||||
defaultOptions: [
|
||||
"never",
|
||||
{
|
||||
exceptRange: false,
|
||||
onlyEquality: false,
|
||||
},
|
||||
],
|
||||
|
||||
docs: {
|
||||
description: 'Require or disallow "Yoda" conditions',
|
||||
recommended: false,
|
||||
frozen: true,
|
||||
url: "https://eslint.org/docs/latest/rules/yoda",
|
||||
},
|
||||
|
||||
schema: [
|
||||
{
|
||||
enum: ["always", "never"],
|
||||
},
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
exceptRange: {
|
||||
type: "boolean",
|
||||
},
|
||||
onlyEquality: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
],
|
||||
|
||||
fixable: "code",
|
||||
messages: {
|
||||
expected:
|
||||
"Expected literal to be on the {{expectedSide}} side of {{operator}}.",
|
||||
},
|
||||
},
|
||||
|
||||
create(context) {
|
||||
const [when, { exceptRange, onlyEquality }] = context.options;
|
||||
const always = when === "always";
|
||||
const sourceCode = context.sourceCode;
|
||||
|
||||
/**
|
||||
* Determines whether node represents a range test.
|
||||
* A range test is a "between" test like `(0 <= x && x < 1)` or an "outside"
|
||||
* test like `(x < 0 || 1 <= x)`. It must be wrapped in parentheses, and
|
||||
* both operators must be `<` or `<=`. Finally, the literal on the left side
|
||||
* must be less than or equal to the literal on the right side so that the
|
||||
* test makes any sense.
|
||||
* @param {ASTNode} node LogicalExpression node to test.
|
||||
* @returns {boolean} Whether node is a range test.
|
||||
*/
|
||||
function isRangeTest(node) {
|
||||
const left = node.left,
|
||||
right = node.right;
|
||||
|
||||
/**
|
||||
* Determines whether node is of the form `0 <= x && x < 1`.
|
||||
* @returns {boolean} Whether node is a "between" range test.
|
||||
*/
|
||||
function isBetweenTest() {
|
||||
if (
|
||||
node.operator === "&&" &&
|
||||
astUtils.isSameReference(left.right, right.left)
|
||||
) {
|
||||
const leftLiteral = getNormalizedLiteral(left.left);
|
||||
const rightLiteral = getNormalizedLiteral(right.right);
|
||||
|
||||
if (leftLiteral === null && rightLiteral === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (rightLiteral === null || leftLiteral === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (leftLiteral.value <= rightLiteral.value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether node is of the form `x < 0 || 1 <= x`.
|
||||
* @returns {boolean} Whether node is an "outside" range test.
|
||||
*/
|
||||
function isOutsideTest() {
|
||||
if (
|
||||
node.operator === "||" &&
|
||||
astUtils.isSameReference(left.left, right.right)
|
||||
) {
|
||||
const leftLiteral = getNormalizedLiteral(left.right);
|
||||
const rightLiteral = getNormalizedLiteral(right.left);
|
||||
|
||||
if (leftLiteral === null && rightLiteral === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (rightLiteral === null || leftLiteral === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (leftLiteral.value <= rightLiteral.value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether node is wrapped in parentheses.
|
||||
* @returns {boolean} Whether node is preceded immediately by an open
|
||||
* paren token and followed immediately by a close
|
||||
* paren token.
|
||||
*/
|
||||
function isParenWrapped() {
|
||||
return astUtils.isParenthesised(sourceCode, node);
|
||||
}
|
||||
|
||||
return (
|
||||
node.type === "LogicalExpression" &&
|
||||
left.type === "BinaryExpression" &&
|
||||
right.type === "BinaryExpression" &&
|
||||
isRangeTestOperator(left.operator) &&
|
||||
isRangeTestOperator(right.operator) &&
|
||||
(isBetweenTest() || isOutsideTest()) &&
|
||||
isParenWrapped()
|
||||
);
|
||||
}
|
||||
|
||||
const OPERATOR_FLIP_MAP = {
|
||||
"===": "===",
|
||||
"!==": "!==",
|
||||
"==": "==",
|
||||
"!=": "!=",
|
||||
"<": ">",
|
||||
">": "<",
|
||||
"<=": ">=",
|
||||
">=": "<=",
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a string representation of a BinaryExpression node with its sides/operator flipped around.
|
||||
* @param {ASTNode} node The BinaryExpression node
|
||||
* @returns {string} A string representation of the node with the sides and operator flipped
|
||||
*/
|
||||
function getFlippedString(node) {
|
||||
const operatorToken = sourceCode.getFirstTokenBetween(
|
||||
node.left,
|
||||
node.right,
|
||||
token => token.value === node.operator,
|
||||
);
|
||||
const lastLeftToken = sourceCode.getTokenBefore(operatorToken);
|
||||
const firstRightToken = sourceCode.getTokenAfter(operatorToken);
|
||||
|
||||
const source = sourceCode.getText();
|
||||
|
||||
const leftText = source.slice(
|
||||
node.range[0],
|
||||
lastLeftToken.range[1],
|
||||
);
|
||||
const textBeforeOperator = source.slice(
|
||||
lastLeftToken.range[1],
|
||||
operatorToken.range[0],
|
||||
);
|
||||
const textAfterOperator = source.slice(
|
||||
operatorToken.range[1],
|
||||
firstRightToken.range[0],
|
||||
);
|
||||
const rightText = source.slice(
|
||||
firstRightToken.range[0],
|
||||
node.range[1],
|
||||
);
|
||||
|
||||
const tokenBefore = sourceCode.getTokenBefore(node);
|
||||
const tokenAfter = sourceCode.getTokenAfter(node);
|
||||
let prefix = "";
|
||||
let suffix = "";
|
||||
|
||||
if (
|
||||
tokenBefore &&
|
||||
tokenBefore.range[1] === node.range[0] &&
|
||||
!astUtils.canTokensBeAdjacent(tokenBefore, firstRightToken)
|
||||
) {
|
||||
prefix = " ";
|
||||
}
|
||||
|
||||
if (
|
||||
tokenAfter &&
|
||||
node.range[1] === tokenAfter.range[0] &&
|
||||
!astUtils.canTokensBeAdjacent(lastLeftToken, tokenAfter)
|
||||
) {
|
||||
suffix = " ";
|
||||
}
|
||||
|
||||
return (
|
||||
prefix +
|
||||
rightText +
|
||||
textBeforeOperator +
|
||||
OPERATOR_FLIP_MAP[operatorToken.value] +
|
||||
textAfterOperator +
|
||||
leftText +
|
||||
suffix
|
||||
);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
return {
|
||||
BinaryExpression(node) {
|
||||
const expectedLiteral = always ? node.left : node.right;
|
||||
const expectedNonLiteral = always ? node.right : node.left;
|
||||
|
||||
// If `expectedLiteral` is not a literal, and `expectedNonLiteral` is a literal, raise an error.
|
||||
if (
|
||||
(expectedNonLiteral.type === "Literal" ||
|
||||
looksLikeLiteral(expectedNonLiteral)) &&
|
||||
!(
|
||||
expectedLiteral.type === "Literal" ||
|
||||
looksLikeLiteral(expectedLiteral)
|
||||
) &&
|
||||
!(!isEqualityOperator(node.operator) && onlyEquality) &&
|
||||
isComparisonOperator(node.operator) &&
|
||||
!(exceptRange && isRangeTest(node.parent))
|
||||
) {
|
||||
context.report({
|
||||
node,
|
||||
messageId: "expected",
|
||||
data: {
|
||||
operator: node.operator,
|
||||
expectedSide: always ? "left" : "right",
|
||||
},
|
||||
fix: fixer =>
|
||||
fixer.replaceText(node, getFlippedString(node)),
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* @fileoverview This file exports everything for this package.
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
|
||||
export * from "./node-hfs.js";
|
||||
export { Hfs } from "@humanfs/core";
|
||||
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"name": "eslint-scope",
|
||||
"description": "ECMAScript scope analyzer for ESLint",
|
||||
"homepage": "https://github.com/eslint/js/blob/main/packages/eslint-scope/README.md",
|
||||
"main": "./dist/eslint-scope.cjs",
|
||||
"types": "./lib/index.d.cts",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": {
|
||||
"types": "./lib/index.d.ts",
|
||||
"default": "./lib/index.js"
|
||||
},
|
||||
"require": {
|
||||
"types": "./lib/index.d.cts",
|
||||
"default": "./dist/eslint-scope.cjs"
|
||||
}
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"version": "9.1.2",
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.13.0 || >=24"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/eslint/js.git",
|
||||
"directory": "packages/eslint-scope"
|
||||
},
|
||||
"funding": "https://opencollective.com/eslint",
|
||||
"keywords": [
|
||||
"eslint"
|
||||
],
|
||||
"bugs": {
|
||||
"url": "https://github.com/eslint/js/issues"
|
||||
},
|
||||
"license": "BSD-2-Clause",
|
||||
"scripts": {
|
||||
"build": "rollup -c",
|
||||
"lint:types": "attw --pack",
|
||||
"pretest": "npm run build",
|
||||
"test": "node Makefile.js test && npm run test:types",
|
||||
"test:types": "tsc -p tsconfig.json && tsc -p tests/types/tsconfig.json"
|
||||
},
|
||||
"files": [
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
"lib",
|
||||
"dist/eslint-scope.cjs"
|
||||
],
|
||||
"dependencies": {
|
||||
"@types/esrecurse": "^4.3.1",
|
||||
"@types/estree": "^1.0.8",
|
||||
"esrecurse": "^4.3.0",
|
||||
"estraverse": "^5.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/parser": "^8.7.0",
|
||||
"chai": "^6.0.0",
|
||||
"eslint": ">=10.0.0-rc.0 <10.0.0 || ^10.0.0",
|
||||
"eslint-visitor-keys": "^5.0.1",
|
||||
"espree": "^11.2.0",
|
||||
"npm-license": "^0.3.3",
|
||||
"shelljs": "^0.8.5"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,89 @@
|
||||
import regeneratorDefine from "./regeneratorDefine.js";
|
||||
function _regenerator() {
|
||||
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */
|
||||
var e,
|
||||
t,
|
||||
r = "function" == typeof Symbol ? Symbol : {},
|
||||
n = r.iterator || "@@iterator",
|
||||
o = r.toStringTag || "@@toStringTag";
|
||||
function i(r, n, o, i) {
|
||||
var c = n && n.prototype instanceof Generator ? n : Generator,
|
||||
u = Object.create(c.prototype);
|
||||
return regeneratorDefine(u, "_invoke", function (r, n, o) {
|
||||
var i,
|
||||
c,
|
||||
u,
|
||||
f = 0,
|
||||
p = o || [],
|
||||
y = !1,
|
||||
G = {
|
||||
p: 0,
|
||||
n: 0,
|
||||
v: e,
|
||||
a: d,
|
||||
f: d.bind(e, 4),
|
||||
d: function d(t, r) {
|
||||
return i = t, c = 0, u = e, G.n = r, a;
|
||||
}
|
||||
};
|
||||
function d(r, n) {
|
||||
for (c = r, u = n, t = 0; !y && f && !o && t < p.length; t++) {
|
||||
var o,
|
||||
i = p[t],
|
||||
d = G.p,
|
||||
l = i[2];
|
||||
r > 3 ? (o = l === n) && (u = i[(c = i[4]) ? 5 : (c = 3, 3)], i[4] = i[5] = e) : i[0] <= d && ((o = r < 2 && d < i[1]) ? (c = 0, G.v = n, G.n = i[1]) : d < l && (o = r < 3 || i[0] > n || n > l) && (i[4] = r, i[5] = n, G.n = l, c = 0));
|
||||
}
|
||||
if (o || r > 1) return a;
|
||||
throw y = !0, n;
|
||||
}
|
||||
return function (o, p, l) {
|
||||
if (f > 1) throw TypeError("Generator is already running");
|
||||
for (y && 1 === p && d(p, l), c = p, u = l; (t = c < 2 ? e : u) || !y;) {
|
||||
i || (c ? c < 3 ? (c > 1 && (G.n = -1), d(c, u)) : G.n = u : G.v = u);
|
||||
try {
|
||||
if (f = 2, i) {
|
||||
if (c || (o = "next"), t = i[o]) {
|
||||
if (!(t = t.call(i, u))) throw TypeError("iterator result is not an object");
|
||||
if (!t.done) return t;
|
||||
u = t.value, c < 2 && (c = 0);
|
||||
} else 1 === c && (t = i["return"]) && t.call(i), c < 2 && (u = TypeError("The iterator does not provide a '" + o + "' method"), c = 1);
|
||||
i = e;
|
||||
} else if ((t = (y = G.n < 0) ? u : r.call(n, G)) !== a) break;
|
||||
} catch (t) {
|
||||
i = e, c = 1, u = t;
|
||||
} finally {
|
||||
f = 1;
|
||||
}
|
||||
}
|
||||
return {
|
||||
value: t,
|
||||
done: y
|
||||
};
|
||||
};
|
||||
}(r, o, i), !0), u;
|
||||
}
|
||||
var a = {};
|
||||
function Generator() {}
|
||||
function GeneratorFunction() {}
|
||||
function GeneratorFunctionPrototype() {}
|
||||
t = Object.getPrototypeOf;
|
||||
var c = [][n] ? t(t([][n]())) : (regeneratorDefine(t = {}, n, function () {
|
||||
return this;
|
||||
}), t),
|
||||
u = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(c);
|
||||
function f(e) {
|
||||
return Object.setPrototypeOf ? Object.setPrototypeOf(e, GeneratorFunctionPrototype) : (e.__proto__ = GeneratorFunctionPrototype, regeneratorDefine(e, o, "GeneratorFunction")), e.prototype = Object.create(u), e;
|
||||
}
|
||||
return GeneratorFunction.prototype = GeneratorFunctionPrototype, regeneratorDefine(u, "constructor", GeneratorFunctionPrototype), regeneratorDefine(GeneratorFunctionPrototype, "constructor", GeneratorFunction), GeneratorFunction.displayName = "GeneratorFunction", regeneratorDefine(GeneratorFunctionPrototype, o, "GeneratorFunction"), regeneratorDefine(u), regeneratorDefine(u, o, "Generator"), regeneratorDefine(u, n, function () {
|
||||
return this;
|
||||
}), regeneratorDefine(u, "toString", function () {
|
||||
return "[object Generator]";
|
||||
}), (_regenerator = function _regenerator() {
|
||||
return {
|
||||
w: i,
|
||||
m: f
|
||||
};
|
||||
})();
|
||||
}
|
||||
export { _regenerator as default };
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAkBA,eAAO,MAAM,aAAa,SAAU,CAAA;AAYpC,eAAO,MAAM,oBAAoB,UAAY,CAAA;AAwD7C,MAAM,MAAM,qBAAqB,GAAG;IAClC,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,qBAA0B,YAkBtE"}
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "shebang-command",
|
||||
"version": "2.0.0",
|
||||
"description": "Get the command from a shebang",
|
||||
"license": "MIT",
|
||||
"repository": "kevva/shebang-command",
|
||||
"author": {
|
||||
"name": "Kevin Mårtensson",
|
||||
"email": "kevinmartensson@gmail.com",
|
||||
"url": "github.com/kevva"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "xo && ava"
|
||||
},
|
||||
"files": [
|
||||
"index.js"
|
||||
],
|
||||
"keywords": [
|
||||
"cmd",
|
||||
"command",
|
||||
"parse",
|
||||
"shebang"
|
||||
],
|
||||
"dependencies": {
|
||||
"shebang-regex": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ava": "^2.3.0",
|
||||
"xo": "^0.24.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,416 @@
|
||||
/**
|
||||
* @fileoverview A rule to choose between single and double quote marks
|
||||
* @author Matt DuVall <http://www.mattduvall.com/>, Brandon Payton
|
||||
* @deprecated in ESLint v8.53.0
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const astUtils = require("./utils/ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Constants
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const QUOTE_SETTINGS = {
|
||||
double: {
|
||||
quote: '"',
|
||||
alternateQuote: "'",
|
||||
description: "doublequote",
|
||||
},
|
||||
single: {
|
||||
quote: "'",
|
||||
alternateQuote: '"',
|
||||
description: "singlequote",
|
||||
},
|
||||
backtick: {
|
||||
quote: "`",
|
||||
alternateQuote: '"',
|
||||
description: "backtick",
|
||||
},
|
||||
};
|
||||
|
||||
// An unescaped newline is a newline preceded by an even number of backslashes.
|
||||
const UNESCAPED_LINEBREAK_PATTERN = new RegExp(
|
||||
String.raw`(^|[^\\])(\\\\)*[${Array.from(astUtils.LINEBREAKS).join("")}]`,
|
||||
"u",
|
||||
);
|
||||
|
||||
/**
|
||||
* Switches quoting of javascript string between ' " and `
|
||||
* escaping and unescaping as necessary.
|
||||
* Only escaping of the minimal set of characters is changed.
|
||||
* Note: escaping of newlines when switching from backtick to other quotes is not handled.
|
||||
* @param {string} str A string to convert.
|
||||
* @returns {string} The string with changed quotes.
|
||||
* @private
|
||||
*/
|
||||
QUOTE_SETTINGS.double.convert =
|
||||
QUOTE_SETTINGS.single.convert =
|
||||
QUOTE_SETTINGS.backtick.convert =
|
||||
function (str) {
|
||||
const newQuote = this.quote;
|
||||
const oldQuote = str[0];
|
||||
|
||||
if (newQuote === oldQuote) {
|
||||
return str;
|
||||
}
|
||||
return (
|
||||
newQuote +
|
||||
str
|
||||
.slice(1, -1)
|
||||
.replace(
|
||||
/\\(\$\{|\r\n?|\n|.)|["'`]|\$\{|(\r\n?|\n)/gu,
|
||||
(match, escaped, newline) => {
|
||||
if (
|
||||
escaped === oldQuote ||
|
||||
(oldQuote === "`" && escaped === "${")
|
||||
) {
|
||||
return escaped; // unescape
|
||||
}
|
||||
if (
|
||||
match === newQuote ||
|
||||
(newQuote === "`" && match === "${")
|
||||
) {
|
||||
return `\\${match}`; // escape
|
||||
}
|
||||
if (newline && oldQuote === "`") {
|
||||
return "\\n"; // escape newlines
|
||||
}
|
||||
return match;
|
||||
},
|
||||
) +
|
||||
newQuote
|
||||
);
|
||||
};
|
||||
|
||||
const AVOID_ESCAPE = "avoid-escape";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @type {import('../types').Rule.RuleModule} */
|
||||
module.exports = {
|
||||
meta: {
|
||||
deprecated: {
|
||||
message: "Formatting rules are being moved out of ESLint core.",
|
||||
url: "https://eslint.org/blog/2023/10/deprecating-formatting-rules/",
|
||||
deprecatedSince: "8.53.0",
|
||||
availableUntil: "11.0.0",
|
||||
replacedBy: [
|
||||
{
|
||||
message:
|
||||
"ESLint Stylistic now maintains deprecated stylistic core rules.",
|
||||
url: "https://eslint.style/guide/migration",
|
||||
plugin: {
|
||||
name: "@stylistic/eslint-plugin",
|
||||
url: "https://eslint.style",
|
||||
},
|
||||
rule: {
|
||||
name: "quotes",
|
||||
url: "https://eslint.style/rules/quotes",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
type: "layout",
|
||||
|
||||
docs: {
|
||||
description:
|
||||
"Enforce the consistent use of either backticks, double, or single quotes",
|
||||
recommended: false,
|
||||
url: "https://eslint.org/docs/latest/rules/quotes",
|
||||
},
|
||||
|
||||
fixable: "code",
|
||||
|
||||
schema: [
|
||||
{
|
||||
enum: ["single", "double", "backtick"],
|
||||
},
|
||||
{
|
||||
anyOf: [
|
||||
{
|
||||
enum: ["avoid-escape"],
|
||||
},
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
avoidEscape: {
|
||||
type: "boolean",
|
||||
},
|
||||
allowTemplateLiterals: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
messages: {
|
||||
wrongQuotes: "Strings must use {{description}}.",
|
||||
},
|
||||
},
|
||||
|
||||
create(context) {
|
||||
const quoteOption = context.options[0],
|
||||
settings = QUOTE_SETTINGS[quoteOption || "double"],
|
||||
options = context.options[1],
|
||||
allowTemplateLiterals =
|
||||
options && options.allowTemplateLiterals === true,
|
||||
sourceCode = context.sourceCode;
|
||||
let avoidEscape = options && options.avoidEscape === true;
|
||||
|
||||
// deprecated
|
||||
if (options === AVOID_ESCAPE) {
|
||||
avoidEscape = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a given node is part of JSX syntax.
|
||||
*
|
||||
* This function returns `true` in the following cases:
|
||||
*
|
||||
* - `<div className="foo"></div>` ... If the literal is an attribute value, the parent of the literal is `JSXAttribute`.
|
||||
* - `<div>foo</div>` ... If the literal is a text content, the parent of the literal is `JSXElement`.
|
||||
* - `<>foo</>` ... If the literal is a text content, the parent of the literal is `JSXFragment`.
|
||||
*
|
||||
* In particular, this function returns `false` in the following cases:
|
||||
*
|
||||
* - `<div className={"foo"}></div>`
|
||||
* - `<div>{"foo"}</div>`
|
||||
*
|
||||
* In both cases, inside of the braces is handled as normal JavaScript.
|
||||
* The braces are `JSXExpressionContainer` nodes.
|
||||
* @param {ASTNode} node The Literal node to check.
|
||||
* @returns {boolean} True if the node is a part of JSX, false if not.
|
||||
* @private
|
||||
*/
|
||||
function isJSXLiteral(node) {
|
||||
return (
|
||||
node.parent.type === "JSXAttribute" ||
|
||||
node.parent.type === "JSXElement" ||
|
||||
node.parent.type === "JSXFragment"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not a given node is a directive.
|
||||
* The directive is a `ExpressionStatement` which has only a string literal not surrounded by
|
||||
* parentheses.
|
||||
* @param {ASTNode} node A node to check.
|
||||
* @returns {boolean} Whether or not the node is a directive.
|
||||
* @private
|
||||
*/
|
||||
function isDirective(node) {
|
||||
return (
|
||||
node.type === "ExpressionStatement" &&
|
||||
node.expression.type === "Literal" &&
|
||||
typeof node.expression.value === "string" &&
|
||||
!astUtils.isParenthesised(sourceCode, node.expression)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a specified node is either part of, or immediately follows a (possibly empty) directive prologue.
|
||||
* @see {@link https://262.ecma-international.org/6.0/#sec-directive-prologues-and-the-use-strict-directive}
|
||||
* @param {ASTNode} node A node to check.
|
||||
* @returns {boolean} Whether a specified node is either part of, or immediately follows a (possibly empty) directive prologue.
|
||||
* @private
|
||||
*/
|
||||
function isExpressionInOrJustAfterDirectivePrologue(node) {
|
||||
if (!astUtils.isTopLevelExpressionStatement(node.parent)) {
|
||||
return false;
|
||||
}
|
||||
const block = node.parent.parent;
|
||||
|
||||
// Check the node is at a prologue.
|
||||
for (let i = 0; i < block.body.length; ++i) {
|
||||
const statement = block.body[i];
|
||||
|
||||
if (statement === node.parent) {
|
||||
return true;
|
||||
}
|
||||
if (!isDirective(statement)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not a given node is allowed as non backtick.
|
||||
* @param {ASTNode} node A node to check.
|
||||
* @returns {boolean} Whether or not the node is allowed as non backtick.
|
||||
* @private
|
||||
*/
|
||||
function isAllowedAsNonBacktick(node) {
|
||||
const parent = node.parent;
|
||||
|
||||
switch (parent.type) {
|
||||
// Directive Prologues.
|
||||
case "ExpressionStatement":
|
||||
return (
|
||||
!astUtils.isParenthesised(sourceCode, node) &&
|
||||
isExpressionInOrJustAfterDirectivePrologue(node)
|
||||
);
|
||||
|
||||
// LiteralPropertyName.
|
||||
case "Property":
|
||||
case "PropertyDefinition":
|
||||
case "MethodDefinition":
|
||||
return parent.key === node && !parent.computed;
|
||||
|
||||
// ModuleSpecifier.
|
||||
case "ImportDeclaration":
|
||||
case "ExportNamedDeclaration":
|
||||
return parent.source === node;
|
||||
|
||||
// ModuleExportName or ModuleSpecifier.
|
||||
case "ExportAllDeclaration":
|
||||
return parent.exported === node || parent.source === node;
|
||||
|
||||
// ModuleExportName.
|
||||
case "ImportSpecifier":
|
||||
return parent.imported === node;
|
||||
|
||||
// ModuleExportName.
|
||||
case "ExportSpecifier":
|
||||
return parent.local === node || parent.exported === node;
|
||||
|
||||
// Others don't allow.
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not a given TemplateLiteral node is actually using any of the special features provided by template literal strings.
|
||||
* @param {ASTNode} node A TemplateLiteral node to check.
|
||||
* @returns {boolean} Whether or not the TemplateLiteral node is using any of the special features provided by template literal strings.
|
||||
* @private
|
||||
*/
|
||||
function isUsingFeatureOfTemplateLiteral(node) {
|
||||
const hasTag =
|
||||
node.parent.type === "TaggedTemplateExpression" &&
|
||||
node === node.parent.quasi;
|
||||
|
||||
if (hasTag) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const hasStringInterpolation = node.expressions.length > 0;
|
||||
|
||||
if (hasStringInterpolation) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const isMultilineString =
|
||||
node.quasis.length >= 1 &&
|
||||
UNESCAPED_LINEBREAK_PATTERN.test(node.quasis[0].value.raw);
|
||||
|
||||
if (isMultilineString) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return {
|
||||
Literal(node) {
|
||||
const val = node.value,
|
||||
rawVal = node.raw;
|
||||
|
||||
if (settings && typeof val === "string") {
|
||||
let isValid =
|
||||
(quoteOption === "backtick" &&
|
||||
isAllowedAsNonBacktick(node)) ||
|
||||
isJSXLiteral(node) ||
|
||||
astUtils.isSurroundedBy(rawVal, settings.quote);
|
||||
|
||||
if (!isValid && avoidEscape) {
|
||||
isValid =
|
||||
astUtils.isSurroundedBy(
|
||||
rawVal,
|
||||
settings.alternateQuote,
|
||||
) && rawVal.includes(settings.quote);
|
||||
}
|
||||
|
||||
if (!isValid) {
|
||||
context.report({
|
||||
node,
|
||||
messageId: "wrongQuotes",
|
||||
data: {
|
||||
description: settings.description,
|
||||
},
|
||||
fix(fixer) {
|
||||
if (
|
||||
quoteOption === "backtick" &&
|
||||
astUtils.hasOctalOrNonOctalDecimalEscapeSequence(
|
||||
rawVal,
|
||||
)
|
||||
) {
|
||||
/*
|
||||
* An octal or non-octal decimal escape sequence in a template literal would
|
||||
* produce syntax error, even in non-strict mode.
|
||||
*/
|
||||
return null;
|
||||
}
|
||||
|
||||
return fixer.replaceText(
|
||||
node,
|
||||
settings.convert(node.raw),
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
TemplateLiteral(node) {
|
||||
// Don't throw an error if backticks are expected or a template literal feature is in use.
|
||||
if (
|
||||
allowTemplateLiterals ||
|
||||
quoteOption === "backtick" ||
|
||||
isUsingFeatureOfTemplateLiteral(node)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
context.report({
|
||||
node,
|
||||
messageId: "wrongQuotes",
|
||||
data: {
|
||||
description: settings.description,
|
||||
},
|
||||
fix(fixer) {
|
||||
if (
|
||||
astUtils.isTopLevelExpressionStatement(
|
||||
node.parent,
|
||||
) &&
|
||||
!astUtils.isParenthesised(sourceCode, node)
|
||||
) {
|
||||
/*
|
||||
* TemplateLiterals aren't actually directives, but fixing them might turn
|
||||
* them into directives and change the behavior of the code.
|
||||
*/
|
||||
return null;
|
||||
}
|
||||
return fixer.replaceText(
|
||||
node,
|
||||
settings.convert(sourceCode.getText(node)),
|
||||
);
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,467 @@
|
||||
// @ts-self-types="./index.d.ts"
|
||||
/**
|
||||
* @fileoverview Merge Strategy
|
||||
*/
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Class
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Container class for several different merge strategies.
|
||||
*/
|
||||
class MergeStrategy {
|
||||
/**
|
||||
* Merges two keys by overwriting the first with the second.
|
||||
* @template TValue1 The type of the value from the first object key.
|
||||
* @template TValue2 The type of the value from the second object key.
|
||||
* @param {TValue1} value1 The value from the first object key.
|
||||
* @param {TValue2} value2 The value from the second object key.
|
||||
* @returns {TValue2} The second value.
|
||||
*/
|
||||
static overwrite(value1, value2) {
|
||||
return value2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges two keys by replacing the first with the second only if the
|
||||
* second is defined.
|
||||
* @template TValue1 The type of the value from the first object key.
|
||||
* @template TValue2 The type of the value from the second object key.
|
||||
* @param {TValue1} value1 The value from the first object key.
|
||||
* @param {TValue2} value2 The value from the second object key.
|
||||
* @returns {TValue1 | TValue2} The second value if it is defined.
|
||||
*/
|
||||
static replace(value1, value2) {
|
||||
if (typeof value2 !== "undefined") {
|
||||
return value2;
|
||||
}
|
||||
|
||||
return value1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges two properties by assigning properties from the second to the first.
|
||||
* @template {Record<string | number | symbol, unknown> | undefined} TValue1 The type of the value from the first object key.
|
||||
* @template {Record<string | number | symbol, unknown>} TValue2 The type of the value from the second object key.
|
||||
* @param {TValue1} value1 The value from the first object key.
|
||||
* @param {TValue2} value2 The value from the second object key.
|
||||
* @returns {Omit<TValue1, keyof TValue2> & TValue2} A new object containing properties from both value1 and
|
||||
* value2.
|
||||
*/
|
||||
static assign(value1, value2) {
|
||||
return Object.assign({}, value1, value2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @fileoverview Validation Strategy
|
||||
*/
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Class
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Container class for several different validation strategies.
|
||||
*/
|
||||
class ValidationStrategy {
|
||||
/**
|
||||
* Validates that a value is an array.
|
||||
* @param {unknown} value The value to validate.
|
||||
* @returns {void}
|
||||
* @throws {TypeError} If the value is invalid.
|
||||
*/
|
||||
static array(value) {
|
||||
if (!Array.isArray(value)) {
|
||||
throw new TypeError("Expected an array.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that a value is a boolean.
|
||||
* @param {unknown} value The value to validate.
|
||||
* @returns {void}
|
||||
* @throws {TypeError} If the value is invalid.
|
||||
*/
|
||||
static boolean(value) {
|
||||
if (typeof value !== "boolean") {
|
||||
throw new TypeError("Expected a boolean.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that a value is a number.
|
||||
* @param {unknown} value The value to validate.
|
||||
* @returns {void}
|
||||
* @throws {TypeError} If the value is invalid.
|
||||
*/
|
||||
static number(value) {
|
||||
if (typeof value !== "number") {
|
||||
throw new TypeError("Expected a number.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that a value is an object.
|
||||
* @param {unknown} value The value to validate.
|
||||
* @returns {void}
|
||||
* @throws {TypeError} If the value is invalid.
|
||||
*/
|
||||
static object(value) {
|
||||
if (!value || typeof value !== "object") {
|
||||
throw new TypeError("Expected an object.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that a value is an object or null.
|
||||
* @param {unknown} value The value to validate.
|
||||
* @returns {void}
|
||||
* @throws {TypeError} If the value is invalid.
|
||||
*/
|
||||
static "object?"(value) {
|
||||
if (typeof value !== "object") {
|
||||
throw new TypeError("Expected an object or null.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that a value is a string.
|
||||
* @param {unknown} value The value to validate.
|
||||
* @returns {void}
|
||||
* @throws {TypeError} If the value is invalid.
|
||||
*/
|
||||
static string(value) {
|
||||
if (typeof value !== "string") {
|
||||
throw new TypeError("Expected a string.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that a value is a non-empty string.
|
||||
* @param {unknown} value The value to validate.
|
||||
* @returns {void}
|
||||
* @throws {TypeError} If the value is invalid.
|
||||
*/
|
||||
static "string!"(value) {
|
||||
if (typeof value !== "string" || value.length === 0) {
|
||||
throw new TypeError("Expected a non-empty string.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @fileoverview Object Schema
|
||||
*/
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Types
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/** @import * as $typests from "./types.ts"; */
|
||||
/** @typedef {$typests.BuiltInMergeStrategy} BuiltInMergeStrategy */
|
||||
/** @typedef {$typests.BuiltInValidationStrategy} BuiltInValidationStrategy */
|
||||
/** @typedef {$typests.CustomMergeStrategy} CustomMergeStrategy */
|
||||
/** @typedef {$typests.CustomValidationStrategy} CustomValidationStrategy */
|
||||
/** @typedef {$typests.ObjectDefinition} ObjectDefinition */
|
||||
/** @typedef {$typests.PropertyDefinition} PropertyDefinition */
|
||||
/** @typedef {$typests.PropertyDefinitionWithSchema} PropertyDefinitionWithSchema */
|
||||
/** @typedef {$typests.PropertyDefinitionWithStrategies} PropertyDefinitionWithStrategies */
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Private
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Validates a schema strategy.
|
||||
* @param {string} name The name of the key this strategy is for.
|
||||
* @param {PropertyDefinition} definition The strategy for the object key.
|
||||
* @returns {void}
|
||||
* @throws {TypeError} When the strategy is missing a name.
|
||||
* @throws {TypeError} When the strategy is missing a merge() method.
|
||||
* @throws {TypeError} When the strategy is missing a validate() method.
|
||||
*/
|
||||
function validateDefinition(name, definition) {
|
||||
let hasSchema = false;
|
||||
if (definition.schema) {
|
||||
if (typeof definition.schema === "object") {
|
||||
hasSchema = true;
|
||||
} else {
|
||||
throw new TypeError("Schema must be an object.");
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof definition.merge === "string") {
|
||||
if (!(definition.merge in MergeStrategy)) {
|
||||
throw new TypeError(
|
||||
`Definition for key "${name}" missing valid merge strategy.`,
|
||||
);
|
||||
}
|
||||
} else if (!hasSchema && typeof definition.merge !== "function") {
|
||||
throw new TypeError(
|
||||
`Definition for key "${name}" must have a merge property.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof definition.validate === "string") {
|
||||
if (!(definition.validate in ValidationStrategy)) {
|
||||
throw new TypeError(
|
||||
`Definition for key "${name}" missing valid validation strategy.`,
|
||||
);
|
||||
}
|
||||
} else if (!hasSchema && typeof definition.validate !== "function") {
|
||||
throw new TypeError(
|
||||
`Definition for key "${name}" must have a validate() method.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Errors
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Error when an unexpected key is found.
|
||||
*/
|
||||
class UnexpectedKeyError extends Error {
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param {string} key The key that was unexpected.
|
||||
*/
|
||||
constructor(key) {
|
||||
super(`Unexpected key "${key}" found.`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error when a required key is missing.
|
||||
*/
|
||||
class MissingKeyError extends Error {
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param {string} key The key that was missing.
|
||||
*/
|
||||
constructor(key) {
|
||||
super(`Missing required key "${key}".`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error when a key requires other keys that are missing.
|
||||
*/
|
||||
class MissingDependentKeysError extends Error {
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param {string} key The key that was unexpected.
|
||||
* @param {Array<string>} requiredKeys The keys that are required.
|
||||
*/
|
||||
constructor(key, requiredKeys) {
|
||||
super(`Key "${key}" requires keys "${requiredKeys.join('", "')}".`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper error for errors occuring during a merge or validate operation.
|
||||
*/
|
||||
class WrapperError extends Error {
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param {string} key The object key causing the error.
|
||||
* @param {Error} source The source error.
|
||||
*/
|
||||
constructor(key, source) {
|
||||
super(`Key "${key}": ${source.message}`, { cause: source });
|
||||
|
||||
// copy over custom properties that aren't represented
|
||||
for (const sourceKey of Object.keys(source)) {
|
||||
if (!(sourceKey in this)) {
|
||||
this[sourceKey] = source[sourceKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Main
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Represents an object validation/merging schema.
|
||||
*/
|
||||
class ObjectSchema {
|
||||
/**
|
||||
* Track all definitions in the schema by key.
|
||||
* @type {Map<string, PropertyDefinition>}
|
||||
*/
|
||||
#definitions = new Map();
|
||||
|
||||
/**
|
||||
* Separately track any keys that are required for faster validation.
|
||||
* @type {Map<string, PropertyDefinition>}
|
||||
*/
|
||||
#requiredKeys = new Map();
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param {ObjectDefinition} definitions The schema definitions.
|
||||
* @throws {Error} When the definitions are missing or invalid.
|
||||
*/
|
||||
constructor(definitions) {
|
||||
if (!definitions) {
|
||||
throw new Error("Schema definitions missing.");
|
||||
}
|
||||
|
||||
// add in all strategies
|
||||
for (const key of Object.keys(definitions)) {
|
||||
const definition = definitions[key];
|
||||
|
||||
validateDefinition(key, definition);
|
||||
|
||||
let normalizedDefinition = definition;
|
||||
|
||||
// normalize merge and validate methods if subschema is present
|
||||
if (typeof normalizedDefinition.schema === "object") {
|
||||
const schema = new ObjectSchema(normalizedDefinition.schema);
|
||||
normalizedDefinition = {
|
||||
...normalizedDefinition,
|
||||
merge(first = {}, second = {}) {
|
||||
return schema.merge(first, second);
|
||||
},
|
||||
validate(value) {
|
||||
ValidationStrategy.object(value);
|
||||
schema.validate(value);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// normalize the merge method in case there's a string
|
||||
if (typeof normalizedDefinition.merge === "string") {
|
||||
normalizedDefinition = {
|
||||
...normalizedDefinition,
|
||||
merge: MergeStrategy[normalizedDefinition.merge],
|
||||
};
|
||||
}
|
||||
|
||||
// normalize the validate method in case there's a string
|
||||
if (typeof normalizedDefinition.validate === "string") {
|
||||
normalizedDefinition = {
|
||||
...normalizedDefinition,
|
||||
validate: ValidationStrategy[normalizedDefinition.validate],
|
||||
};
|
||||
}
|
||||
|
||||
this.#definitions.set(key, normalizedDefinition);
|
||||
|
||||
if (normalizedDefinition.required) {
|
||||
this.#requiredKeys.set(key, normalizedDefinition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a strategy has been registered for the given object key.
|
||||
* @param {string} key The object key to find a strategy for.
|
||||
* @returns {boolean} True if the key has a strategy registered, false if not.
|
||||
*/
|
||||
hasKey(key) {
|
||||
return this.#definitions.has(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges objects together to create a new object comprised of the keys
|
||||
* of the all objects. Keys are merged based on the each key's merge
|
||||
* strategy.
|
||||
* @param {...Object} objects The objects to merge.
|
||||
* @returns {Object} A new object with a mix of all objects' keys.
|
||||
* @throws {TypeError} If any object is invalid.
|
||||
*/
|
||||
merge(...objects) {
|
||||
// double check arguments
|
||||
if (objects.length < 2) {
|
||||
throw new TypeError("merge() requires at least two arguments.");
|
||||
}
|
||||
|
||||
if (
|
||||
objects.some(
|
||||
object => object === null || typeof object !== "object",
|
||||
)
|
||||
) {
|
||||
throw new TypeError("All arguments must be objects.");
|
||||
}
|
||||
|
||||
return objects.reduce((result, object) => {
|
||||
this.validate(object);
|
||||
|
||||
for (const [key, strategy] of this.#definitions) {
|
||||
try {
|
||||
if (key in result || key in object) {
|
||||
const merge = /** @type {Function} */ (strategy.merge);
|
||||
const value = merge.call(
|
||||
this,
|
||||
result[key],
|
||||
object[key],
|
||||
);
|
||||
if (value !== undefined) {
|
||||
result[key] = value;
|
||||
}
|
||||
}
|
||||
} catch (ex) {
|
||||
throw new WrapperError(key, ex);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates an object's keys based on the validate strategy for each key.
|
||||
* @param {Object} object The object to validate.
|
||||
* @returns {void}
|
||||
* @throws {Error} When the object is invalid.
|
||||
*/
|
||||
validate(object) {
|
||||
// check existing keys first
|
||||
for (const key of Object.keys(object)) {
|
||||
// check to see if the key is defined
|
||||
if (!this.hasKey(key)) {
|
||||
throw new UnexpectedKeyError(key);
|
||||
}
|
||||
|
||||
// validate existing keys
|
||||
const definition = /** @type {PropertyDefinition} */ (
|
||||
this.#definitions.get(key)
|
||||
); // `definition` is guaranteed to exist since we check with `hasKey()` above.
|
||||
|
||||
// first check to see if any other keys are required
|
||||
if (Array.isArray(definition.requires)) {
|
||||
if (
|
||||
!definition.requires.every(otherKey => otherKey in object)
|
||||
) {
|
||||
throw new MissingDependentKeysError(
|
||||
key,
|
||||
definition.requires,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// now apply remaining validation strategy
|
||||
try {
|
||||
const validate = /** @type {Function} */ (definition.validate);
|
||||
validate.call(definition, object[key]);
|
||||
} catch (ex) {
|
||||
throw new WrapperError(key, ex);
|
||||
}
|
||||
}
|
||||
|
||||
// ensure required keys aren't missing
|
||||
for (const [key] of this.#requiredKeys) {
|
||||
if (!(key in object)) {
|
||||
throw new MissingKeyError(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { MergeStrategy, ObjectSchema, ValidationStrategy };
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* @fileoverview JSON reporter
|
||||
* @author Burak Yigit Kaya aka BYK
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Public Interface
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = function (results) {
|
||||
return JSON.stringify(results);
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"author": "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
"name": "which",
|
||||
"description": "Like which(1) unix command. Find the first instance of an executable in the PATH.",
|
||||
"version": "2.0.2",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/isaacs/node-which.git"
|
||||
},
|
||||
"main": "which.js",
|
||||
"bin": {
|
||||
"node-which": "./bin/node-which"
|
||||
},
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"isexe": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mkdirp": "^0.5.0",
|
||||
"rimraf": "^2.6.2",
|
||||
"tap": "^14.6.9"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "tap",
|
||||
"preversion": "npm test",
|
||||
"postversion": "npm publish",
|
||||
"prepublish": "npm run changelog",
|
||||
"prechangelog": "bash gen-changelog.sh",
|
||||
"changelog": "git add CHANGELOG.md",
|
||||
"postchangelog": "git commit -m 'update changelog - '${npm_package_version}",
|
||||
"postpublish": "git push origin --follow-tags"
|
||||
},
|
||||
"files": [
|
||||
"which.js",
|
||||
"bin/node-which"
|
||||
],
|
||||
"tap": {
|
||||
"check-coverage": true
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
var temporalUndefined = require("./temporalUndefined.js");
|
||||
var tdz = require("./tdz.js");
|
||||
function _temporalRef(r, e) {
|
||||
return r === temporalUndefined ? tdz(e) : r;
|
||||
}
|
||||
module.exports = _temporalRef, module.exports.__esModule = true, module.exports["default"] = module.exports;
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,23 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const shebangCommand = require('shebang-command');
|
||||
|
||||
function readShebang(command) {
|
||||
// Read the first 150 bytes from the file
|
||||
const size = 150;
|
||||
const buffer = Buffer.alloc(size);
|
||||
|
||||
let fd;
|
||||
|
||||
try {
|
||||
fd = fs.openSync(command, 'r');
|
||||
fs.readSync(fd, buffer, 0, size, 0);
|
||||
fs.closeSync(fd);
|
||||
} catch (e) { /* Empty */ }
|
||||
|
||||
// Attempt to extract shebang (null is returned if not a shebang)
|
||||
return shebangCommand(buffer.toString());
|
||||
}
|
||||
|
||||
module.exports = readShebang;
|
||||
@@ -0,0 +1,55 @@
|
||||
'use strict'
|
||||
|
||||
const { readFileSync } = require('node:fs')
|
||||
const vm = require('vm')
|
||||
const { join } = require('node:path')
|
||||
const code = readFileSync(
|
||||
join(__dirname, '..', '..', 'node_modules', 'loglevel', 'lib', 'loglevel.js')
|
||||
)
|
||||
const { Console } = require('console')
|
||||
|
||||
function build (dest) {
|
||||
const sandbox = {
|
||||
module: {},
|
||||
console: new Console(dest, dest)
|
||||
}
|
||||
const context = vm.createContext(sandbox)
|
||||
|
||||
const script = new vm.Script(code)
|
||||
script.runInContext(context)
|
||||
|
||||
const loglevel = sandbox.log
|
||||
|
||||
const originalFactory = loglevel.methodFactory
|
||||
loglevel.methodFactory = function (methodName, logLevel, loggerName) {
|
||||
const rawMethod = originalFactory(methodName, logLevel, loggerName)
|
||||
|
||||
return function () {
|
||||
const time = new Date()
|
||||
let array
|
||||
if (typeof arguments[0] === 'string') {
|
||||
arguments[0] = '[' + time.toISOString() + '] ' + arguments[0]
|
||||
rawMethod.apply(null, arguments)
|
||||
} else {
|
||||
array = new Array(arguments.length + 1)
|
||||
array[0] = '[' + time.toISOString() + ']'
|
||||
for (var i = 0; i < arguments.length; i++) {
|
||||
array[i + 1] = arguments[i]
|
||||
}
|
||||
rawMethod.apply(null, array)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loglevel.setLevel(loglevel.levels.INFO)
|
||||
return loglevel
|
||||
}
|
||||
|
||||
module.exports = build
|
||||
|
||||
if (require.main === module) {
|
||||
const loglevel = build(process.stdout)
|
||||
loglevel.info('hello')
|
||||
loglevel.info({ hello: 'world' })
|
||||
loglevel.info('hello %j', { hello: 'world' })
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import type { ParseSettings } from './index';
|
||||
/**
|
||||
* This needs to be kept in sync with package.json in the typescript-eslint monorepo
|
||||
*/
|
||||
export declare const SUPPORTED_TYPESCRIPT_VERSIONS = ">=4.8.4 <6.1.0";
|
||||
export declare function handleUnsupportedTSVersion(parseSettings: ParseSettings, behavior: 'error' | 'ignore' | 'warn', passedLoggerFn: boolean): void;
|
||||
@@ -0,0 +1,23 @@
|
||||
Copyright (c) 2015, Scott Motte
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"JSON.stringify@native": {
|
||||
"name": "JSON.stringify@native",
|
||||
"browser": "IE 9.0.0 (Windows 7 0.0.0)",
|
||||
"suite": "libs",
|
||||
"hz": 9927.615273483276,
|
||||
"success": true,
|
||||
"fastest": false,
|
||||
"rme": 0.012686011907513476,
|
||||
"rhz": 1.8583624088037056,
|
||||
"sampleSize": 164
|
||||
},
|
||||
"fast-stable-stringify@a9f81e8": {
|
||||
"name": "fast-stable-stringify@a9f81e8",
|
||||
"browser": "IE 9.0.0 (Windows 7 0.0.0)",
|
||||
"suite": "libs",
|
||||
"hz": 5342.1309139986515,
|
||||
"success": true,
|
||||
"fastest": true,
|
||||
"rme": 0.013540097031428645,
|
||||
"rhz": 1,
|
||||
"sampleSize": 146
|
||||
},
|
||||
"json-stable-stringify@1.0.1": {
|
||||
"name": "json-stable-stringify@1.0.1",
|
||||
"browser": "IE 9.0.0 (Windows 7 0.0.0)",
|
||||
"suite": "libs",
|
||||
"hz": 3139.8341435516154,
|
||||
"success": true,
|
||||
"fastest": false,
|
||||
"rme": 0.015201173421442258,
|
||||
"rhz": 0.5877493820535017,
|
||||
"sampleSize": 135
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import type { TSESLint } from '@typescript-eslint/utils';
|
||||
export type Options = [
|
||||
{
|
||||
allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing?: boolean;
|
||||
ignoreBooleanCoercion?: boolean;
|
||||
ignoreConditionalTests?: boolean;
|
||||
ignoreIfStatements?: boolean;
|
||||
ignoreMixedLogicalExpressions?: boolean;
|
||||
ignorePrimitives?: true | {
|
||||
bigint?: boolean;
|
||||
boolean?: boolean;
|
||||
number?: boolean;
|
||||
string?: boolean;
|
||||
};
|
||||
ignoreTernaryTests?: boolean;
|
||||
}
|
||||
];
|
||||
export type MessageIds = 'noStrictNullCheck' | 'preferNullishOverAssignment' | 'preferNullishOverOr' | 'preferNullishOverTernary' | 'suggestNullish';
|
||||
declare const _default: TSESLint.RuleModule<MessageIds, Options, import("../../rules").ESLintPluginDocs, TSESLint.RuleListener> & {
|
||||
name: string;
|
||||
};
|
||||
export default _default;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,79 @@
|
||||
'use strict'
|
||||
|
||||
const test = require('node:test')
|
||||
const assert = require('node:assert')
|
||||
const { join } = require('node:path')
|
||||
|
||||
const execa = require('execa')
|
||||
const writer = require('flush-write-stream')
|
||||
const { once } = require('./helper')
|
||||
|
||||
// https://github.com/pinojs/pino/issues/542
|
||||
test('pino.destination log everything when calling process.exit(0)', async () => {
|
||||
let actual = ''
|
||||
const child = execa(process.argv[0], [join(__dirname, 'fixtures', 'destination-exit.js')])
|
||||
|
||||
child.stdout.pipe(writer((s, enc, cb) => {
|
||||
actual += s
|
||||
cb()
|
||||
}))
|
||||
|
||||
await once(child, 'close')
|
||||
|
||||
assert.equal(actual.match(/hello/) != null, true)
|
||||
assert.equal(actual.match(/world/) != null, true)
|
||||
})
|
||||
|
||||
test('pino with no args log everything when calling process.exit(0)', async () => {
|
||||
let actual = ''
|
||||
const child = execa(process.argv[0], [join(__dirname, 'fixtures', 'default-exit.js')])
|
||||
|
||||
child.stdout.pipe(writer((s, enc, cb) => {
|
||||
actual += s
|
||||
cb()
|
||||
}))
|
||||
|
||||
await once(child, 'close')
|
||||
|
||||
assert.equal(actual.match(/hello/) != null, true)
|
||||
assert.equal(actual.match(/world/) != null, true)
|
||||
})
|
||||
|
||||
test('sync false logs everything when calling process.exit(0)', async () => {
|
||||
let actual = ''
|
||||
const child = execa(process.argv[0], [join(__dirname, 'fixtures', 'syncfalse-exit.js')])
|
||||
|
||||
child.stdout.pipe(writer((s, enc, cb) => {
|
||||
actual += s
|
||||
cb()
|
||||
}))
|
||||
|
||||
await once(child, 'close')
|
||||
|
||||
assert.equal(actual.match(/hello/) != null, true)
|
||||
assert.equal(actual.match(/world/) != null, true)
|
||||
})
|
||||
|
||||
test('sync false logs everything when calling flushSync', async () => {
|
||||
let actual = ''
|
||||
const child = execa(process.argv[0], [join(__dirname, 'fixtures', 'syncfalse-flush-exit.js')])
|
||||
|
||||
child.stdout.pipe(writer((s, enc, cb) => {
|
||||
actual += s
|
||||
cb()
|
||||
}))
|
||||
|
||||
await once(child, 'close')
|
||||
|
||||
assert.equal(actual.match(/hello/) != null, true)
|
||||
assert.equal(actual.match(/world/) != null, true)
|
||||
})
|
||||
|
||||
test('transports exits gracefully when logging in exit', async () => {
|
||||
const child = execa(process.argv[0], [join(__dirname, 'fixtures', 'transport-with-on-exit.js')])
|
||||
child.stdout.resume()
|
||||
|
||||
const code = await once(child, 'close')
|
||||
|
||||
assert.equal(code, 0)
|
||||
})
|
||||
@@ -0,0 +1,14 @@
|
||||
import type { TSESLint } from '@typescript-eslint/utils';
|
||||
export type Options = [
|
||||
{
|
||||
caseSensitive?: boolean;
|
||||
checkIntersections?: boolean;
|
||||
checkUnions?: boolean;
|
||||
groupOrder?: string[];
|
||||
}
|
||||
];
|
||||
export type MessageIds = 'notSorted' | 'notSortedNamed' | 'suggestFix';
|
||||
declare const _default: TSESLint.RuleModule<MessageIds, Options, import("../../rules").ESLintPluginDocs, TSESLint.RuleListener> & {
|
||||
name: string;
|
||||
};
|
||||
export default _default;
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 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,12 @@
|
||||
/**
|
||||
* RIPEMD-160 legacy hash function.
|
||||
* https://homes.esat.kuleuven.be/~bosselae/ripemd160.html
|
||||
* https://homes.esat.kuleuven.be/~bosselae/ripemd160/pdf/AB-9601/AB-9601.pdf
|
||||
* @module
|
||||
* @deprecated
|
||||
*/
|
||||
import { RIPEMD160 as RIPEMD160n, ripemd160 as ripemd160n } from './legacy.ts';
|
||||
/** @deprecated Use import from `noble/hashes/legacy` module */
|
||||
export const RIPEMD160: typeof RIPEMD160n = RIPEMD160n;
|
||||
/** @deprecated Use import from `noble/hashes/legacy` module */
|
||||
export const ripemd160: typeof ripemd160n = ripemd160n;
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
|
||||
export { ModuleImporter };
|
||||
import { ModuleImporter } from "./module-importer.cjs";
|
||||
Reference in New Issue
Block a user