Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

recast

Package Overview
Dependencies
Maintainers
1
Versions
266
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

recast - npm Package Compare versions

Comparing version 0.15.5 to 0.16.0

parsers/_babel_options.js

118

lib/fast-path.js

@@ -7,2 +7,3 @@ var assert = require("assert");

var isNumber = types.builtInTypes.number;
var util = require("./util.js");

@@ -184,44 +185,85 @@ function FastPath(value) {

function expressionStartsWithCurlyBrace(node) {
// TODO detect when node is already wrapped in parentheses to avoid (admittedly harmless) wrapping in extra parentheses.
if(node == null) return false;
switch(node.type) {
// expressions guaranteed to start with a {
case "ObjectExpression":
case "ObjectPattern":
return true;
// Returns true if the node at the tip of the path is wrapped with
// parentheses, OR if the only reason the node needed parentheses was that
// it couldn't be the first expression in the enclosing statement (see
// FastPath#canBeFirstInStatement), and it has an opening `(` character.
// For example, the FunctionExpression in `(function(){}())` appears to
// need parentheses only because it's the first expression in the AST, but
// since it happens to be preceded by a `(` (which is not apparent from
// the AST but can be determined using FastPath#getPrevToken), there is no
// ambiguity about how to parse it, so it counts as having parentheses,
// even though it is not immediately followed by a `)`.
FPp.hasParens = function () {
const node = this.getNode();
// All expressions that comprise nested expressions
case "BinaryExpression":
case "AssignmentExpression":
case "LogicalExpression":
return expressionStartsWithCurlyBrace(node.left);
const prevToken = this.getPrevToken(node);
if (! prevToken) {
return false;
}
case "MemberExpression":
case "BindExpression":
return expressionStartsWithCurlyBrace(node.object);
const nextToken = this.getNextToken(node);
if (! nextToken) {
return false;
}
case "CallExpression":
return expressionStartsWithCurlyBrace(node.callee);
if (prevToken.value === "(") {
if (nextToken.value === ")") {
// If the node preceded by a `(` token and followed by a `)` token,
// then of course it has parentheses.
return true;
}
case "UpdateExpression":
case "UnaryExpression":
return node.prefix === false && expressionStartsWithCurlyBrace(node.argument);
// If this is one of the few Expression types that can't come first in
// the enclosing statement because of parsing ambiguities (namely,
// FunctionExpression, ObjectExpression, and ClassExpression) and
// this.firstInStatement() returns true, and the node would not need
// parentheses in an expression context because this.needsParens(true)
// returns false, then it just needs an opening parenthesis to resolve
// the parsing ambiguity that made it appear to need parentheses.
const justNeedsOpeningParen =
! this.canBeFirstInStatement() &&
this.firstInStatement() &&
! this.needsParens(true);
case "TSNonNullExpression":
return expressionStartsWithCurlyBrace(node.expression);
if (justNeedsOpeningParen) {
return true;
}
}
case "ConditionalExpression":
return expressionStartsWithCurlyBrace(node.test);
return false;
};
case "SequenceExpression":
return expressionStartsWithCurlyBrace(node.expressions[0]);
FPp.getPrevToken = function (node) {
node = node || this.getNode();
const loc = node && node.loc;
const tokens = loc && loc.tokens;
if (tokens && loc.start.token > 0) {
const token = tokens[loc.start.token - 1];
if (token) {
// Do not return tokens that fall outside the root subtree.
const rootLoc = this.getRootValue().loc;
if (util.comparePos(rootLoc.start, token.loc.start) <= 0) {
return token;
}
}
}
return null;
};
case "TaggedTemplateExpression":
return expressionStartsWithCurlyBrace(node.tag);
default:
false;
FPp.getNextToken = function (node) {
node = node || this.getNode();
const loc = node && node.loc;
const tokens = loc && loc.tokens;
if (tokens && loc.end.token < tokens.length) {
const token = tokens[loc.end.token];
if (token) {
// Do not return tokens that fall outside the root subtree.
const rootLoc = this.getRootValue().loc;
if (util.comparePos(token.loc.end, rootLoc.end) <= 0) {
return token;
}
}
}
}
return null;
};

@@ -268,6 +310,2 @@ // Inspired by require("ast-types").NodePath.prototype.needsParens, but

if (parent.type === "ArrowFunctionExpression" && name === "body") {
return expressionStartsWithCurlyBrace(node);
}
switch (node.type) {

@@ -544,2 +582,8 @@ case "UnaryExpression":

if (n.ArrowFunctionExpression.check(parent) &&
childName === "body") {
assert.strictEqual(parent.body, child);
return true;
}
if (n.SequenceExpression.check(parent) &&

@@ -546,0 +590,0 @@ parentName === "expressions" &&

var defaults = {
// If you want to use a different branch of esprima, or any other
// module that supports a .parse function, pass that module object to
// recast.parse as options.parser (legacy synonym: options.esprima).
parser: require("../parsers/esprima"),
// If you want to use a different branch of esprima, or any other module
// that supports a .parse function, pass that module object to
// recast.parse as options.parser (legacy synonym: options.esprima).
parser: require("../parsers/esprima"),
// Number of spaces the pretty-printer should use per tab for
// indentation. If you do not pass this option explicitly, it will be
// (quite reliably!) inferred from the original code.
tabWidth: 4,
// Number of spaces the pretty-printer should use per tab for
// indentation. If you do not pass this option explicitly, it will be
// (quite reliably!) inferred from the original code.
tabWidth: 4,
// If you really want the pretty-printer to use tabs instead of
// spaces, make this option true.
useTabs: false,
// If you really want the pretty-printer to use tabs instead of spaces,
// make this option true.
useTabs: false,
// The reprinting code leaves leading whitespace untouched unless it
// has to reindent a line, or you pass false for this option.
reuseWhitespace: true,
// The reprinting code leaves leading whitespace untouched unless it has
// to reindent a line, or you pass false for this option.
reuseWhitespace: true,
// Override this option to use a different line terminator, e.g. \r\n.
lineTerminator: require("os").EOL || "\n",
// Override this option to use a different line terminator, e.g. \r\n.
lineTerminator: require("os").EOL || "\n",
// Some of the pretty-printer code (such as that for printing function
// parameter lists) makes a valiant attempt to prevent really long
// lines. You can adjust the limit by changing this option; however,
// there is no guarantee that line length will fit inside this limit.
wrapColumn: 74, // Aspirational for now.
// Some of the pretty-printer code (such as that for printing function
// parameter lists) makes a valiant attempt to prevent really long
// lines. You can adjust the limit by changing this option; however,
// there is no guarantee that line length will fit inside this limit.
wrapColumn: 74, // Aspirational for now.
// Pass a string as options.sourceFileName to recast.parse to tell the
// reprinter to keep track of reused code so that it can construct a
// source map automatically.
sourceFileName: null,
// Pass a string as options.sourceFileName to recast.parse to tell the
// reprinter to keep track of reused code so that it can construct a
// source map automatically.
sourceFileName: null,
// Pass a string as options.sourceMapName to recast.print, and
// (provided you passed options.sourceFileName earlier) the
// PrintResult of recast.print will have a .map property for the
// generated source map.
sourceMapName: null,
// Pass a string as options.sourceMapName to recast.print, and (provided
// you passed options.sourceFileName earlier) the PrintResult of
// recast.print will have a .map property for the generated source map.
sourceMapName: null,
// If provided, this option will be passed along to the source map
// generator as a root directory for relative source file paths.
sourceRoot: null,
// If provided, this option will be passed along to the source map
// generator as a root directory for relative source file paths.
sourceRoot: null,
// If you provide a source map that was generated from a previous call
// to recast.print as options.inputSourceMap, the old source map will
// be composed with the new source map.
inputSourceMap: null,
// If you provide a source map that was generated from a previous call
// to recast.print as options.inputSourceMap, the old source map will be
// composed with the new source map.
inputSourceMap: null,
// If you want esprima to generate .range information (recast only
// uses .loc internally), pass true for this option.
range: false,
// If you want esprima to generate .range information (recast only uses
// .loc internally), pass true for this option.
range: false,
// If you want esprima not to throw exceptions when it encounters
// non-fatal errors, keep this option true.
tolerant: true,
// If you want esprima not to throw exceptions when it encounters
// non-fatal errors, keep this option true.
tolerant: true,
// If you want to override the quotes used in string literals, specify
// either "single", "double", or "auto" here ("auto" will select the one
// which results in the shorter literal)
// Otherwise, double quotes are used.
quote: null,
// If you want to override the quotes used in string literals, specify
// either "single", "double", or "auto" here ("auto" will select the one
// which results in the shorter literal) Otherwise, use double quotes.
quote: null,
// Controls the printing of trailing commas in object literals,
// array expressions and function parameters.
//
// This option could either be:
// * Boolean - enable/disable in all contexts (objects, arrays and function params).
// * Object - enable/disable per context.
//
// Example:
// trailingComma: {
// objects: true,
// arrays: true,
// parameters: false,
// }
trailingComma: false,
// Controls the printing of trailing commas in object literals, array
// expressions and function parameters.
//
// This option could either be:
// * Boolean - enable/disable in all contexts (objects, arrays and function params).
// * Object - enable/disable per context.
//
// Example:
// trailingComma: {
// objects: true,
// arrays: true,
// parameters: false,
// }
trailingComma: false,
// Controls the printing of spaces inside array brackets.
// See: http://eslint.org/docs/rules/array-bracket-spacing
arrayBracketSpacing: false,
// Controls the printing of spaces inside array brackets.
// See: http://eslint.org/docs/rules/array-bracket-spacing
arrayBracketSpacing: false,
// Controls the printing of spaces inside object literals,
// destructuring assignments, and import/export specifiers.
// See: http://eslint.org/docs/rules/object-curly-spacing
objectCurlySpacing: true,
// Controls the printing of spaces inside object literals,
// destructuring assignments, and import/export specifiers.
// See: http://eslint.org/docs/rules/object-curly-spacing
objectCurlySpacing: true,
// If you want parenthesis to wrap single-argument arrow function parameter
// lists, pass true for this option.
arrowParensAlways: false,
// If you want parenthesis to wrap single-argument arrow function
// parameter lists, pass true for this option.
arrowParensAlways: false,
// There are 2 supported syntaxes (`,` and `;`) in Flow Object Types;
// The use of commas is in line with the more popular style and matches
// how objects are defined in JS, making it a bit more natural to write.
flowObjectCommas: true,
// There are 2 supported syntaxes (`,` and `;`) in Flow Object Types;
// The use of commas is in line with the more popular style and matches
// how objects are defined in JS, making it a bit more natural to write.
flowObjectCommas: true,
// Whether to return an array of .tokens on the root AST node.
tokens: true
}, hasOwn = defaults.hasOwnProperty;

@@ -99,30 +101,31 @@

exports.normalize = function(options) {
options = options || defaults;
options = options || defaults;
function get(key) {
return hasOwn.call(options, key)
? options[key]
: defaults[key];
}
function get(key) {
return hasOwn.call(options, key)
? options[key]
: defaults[key];
}
return {
tabWidth: +get("tabWidth"),
useTabs: !!get("useTabs"),
reuseWhitespace: !!get("reuseWhitespace"),
lineTerminator: get("lineTerminator"),
wrapColumn: Math.max(get("wrapColumn"), 0),
sourceFileName: get("sourceFileName"),
sourceMapName: get("sourceMapName"),
sourceRoot: get("sourceRoot"),
inputSourceMap: get("inputSourceMap"),
parser: get("esprima") || get("parser"),
range: get("range"),
tolerant: get("tolerant"),
quote: get("quote"),
trailingComma: get("trailingComma"),
arrayBracketSpacing: get("arrayBracketSpacing"),
objectCurlySpacing: get("objectCurlySpacing"),
arrowParensAlways: get("arrowParensAlways"),
flowObjectCommas: get("flowObjectCommas"),
};
return {
tabWidth: +get("tabWidth"),
useTabs: !!get("useTabs"),
reuseWhitespace: !!get("reuseWhitespace"),
lineTerminator: get("lineTerminator"),
wrapColumn: Math.max(get("wrapColumn"), 0),
sourceFileName: get("sourceFileName"),
sourceMapName: get("sourceMapName"),
sourceRoot: get("sourceRoot"),
inputSourceMap: get("inputSourceMap"),
parser: get("esprima") || get("parser"),
range: get("range"),
tolerant: get("tolerant"),
quote: get("quote"),
trailingComma: get("trailingComma"),
arrayBracketSpacing: get("arrayBracketSpacing"),
objectCurlySpacing: get("objectCurlySpacing"),
arrowParensAlways: get("arrowParensAlways"),
flowObjectCommas: get("flowObjectCommas"),
tokens: !!get("tokens")
};
};

@@ -40,2 +40,22 @@ "use strict";

// Use ast.tokens if possible, and otherwise fall back to the Esprima
// tokenizer. All the preconfigured ../parsers/* expose ast.tokens
// automatically, but custom parsers might need additional configuration
// to avoid this fallback.
const tokens = Array.isArray(ast.tokens)
? ast.tokens
: require("esprima").tokenize(sourceWithoutTabs, {
loc: true
});
// We will reattach the tokens array to the file object below.
delete ast.tokens;
// Make sure every token has a token.value string.
tokens.forEach(function (token) {
if (typeof token.value !== "string") {
token.value = lines.sliceString(token.loc.start, token.loc.end);
}
});
if (Array.isArray(ast.comments)) {

@@ -80,2 +100,7 @@ comments = ast.comments;

// Expose file.tokens unless the caller passed false for options.tokens.
if (options.tokens) {
file.tokens = tokens;
}
// Expand the Program's .loc to include all comments (not just those

@@ -105,8 +130,11 @@ // attached to the Program node, as its children may have comments as

// compared to the original.
return new TreeCopier(lines).copy(file);
return new TreeCopier(lines, tokens).copy(file);
};
function TreeCopier(lines) {
function TreeCopier(lines, tokens) {
assert.ok(this instanceof TreeCopier);
this.lines = lines;
this.tokens = tokens;
this.startTokenIndex = 0;
this.endTokenIndex = tokens.length;
this.indent = 0;

@@ -153,2 +181,5 @@ this.seen = new Map;

const oldStartTokenIndex = this.startTokenIndex;
const oldEndTokenIndex = this.endTokenIndex;
if (loc) {

@@ -166,4 +197,12 @@ // When node is a comment, we set node.loc.indent to

// Every node.loc has a reference to the original source lines as well
// as a complete list of source tokens.
loc.lines = this.lines;
loc.tokens = this.tokens;
loc.indent = newIndent;
// Set loc.start.token and loc.end.token such that
// loc.tokens.slice(loc.start.token, loc.end.token) returns a list of
// all the tokens that make up this node.
this.findTokenRange(loc);
}

@@ -188,4 +227,57 @@

this.indent = oldIndent;
this.startTokenIndex = oldStartTokenIndex;
this.endTokenIndex = oldEndTokenIndex;
return copy;
};
// If we didn't have any idea where in loc.tokens to look for tokens
// contained by this loc, a binary search would be appropriate, but
// because we maintain this.startTokenIndex and this.endTokenIndex as we
// traverse the AST, we only need to make small (linear) adjustments to
// those indexes with each recursive iteration.
TCp.findTokenRange = function (loc) {
// In the unlikely event that loc.tokens[this.startTokenIndex] starts
// *after* loc.start, we need to rewind this.startTokenIndex first.
while (this.startTokenIndex > 0) {
const token = loc.tokens[this.startTokenIndex];
if (util.comparePos(loc.start, token.loc.start) < 0) {
--this.startTokenIndex;
} else break;
}
// In the unlikely event that loc.tokens[this.endTokenIndex - 1] ends
// *before* loc.end, we need to fast-forward this.endTokenIndex first.
while (this.endTokenIndex < loc.tokens.length) {
const token = loc.tokens[this.endTokenIndex];
if (util.comparePos(token.loc.end, loc.end) < 0) {
++this.endTokenIndex;
} else break;
}
// Increment this.startTokenIndex until we've found the first token
// contained by this node.
while (this.startTokenIndex < this.endTokenIndex) {
const token = loc.tokens[this.startTokenIndex];
if (util.comparePos(token.loc.start, loc.start) < 0) {
++this.startTokenIndex;
} else break;
}
// Index into loc.tokens of the first token within this node.
loc.start.token = this.startTokenIndex;
// Decrement this.endTokenIndex until we've found the first token after
// this node (not contained by the node).
while (this.endTokenIndex > this.startTokenIndex) {
const token = loc.tokens[this.endTokenIndex - 1];
if (util.comparePos(loc.end, token.loc.end) < 0) {
--this.endTokenIndex;
} else break;
}
// Index into loc.tokens of the first token *after* this node.
// If loc.start.token === loc.end.token, the node contains no tokens,
// and the index is that of the next token following this node.
loc.end.token = this.endTokenIndex;
};

@@ -193,6 +193,12 @@ var assert = require("assert");

var newLines = print(
reprint.newPath,
needToPrintNewPathWithComments
).indentTail(oldNode.loc.indent);
var newLines = print(reprint.newPath, {
includeComments: needToPrintNewPathWithComments,
// If the oldNode we're replacing already had parentheses, we may
// not need to print the new node with any extra parentheses,
// because the existing parentheses will suffice. However, if the
// newNode has a different type than the oldNode, let the printer
// decide if reprint.newPath needs parentheses, as usual.
avoidRootParens: (oldNode.type === newNode.type &&
reprint.oldPath.hasParens())
}).indentTail(oldNode.loc.indent);

@@ -220,3 +226,9 @@ var nls = needsLeadingSpace(lines, oldNode.loc, newLines);

// guaranteed to contain all the reprinted nodes and comments.
return patcher.get(origLoc).indentTail(-orig.loc.indent);
const patchedLines = patcher.get(origLoc).indentTail(-orig.loc.indent);
if (path.needsParens()) {
return linesModule.concat(["(", patchedLines, ")"]);
}
return patchedLines;
};

@@ -408,74 +420,2 @@ };

// This object is reused in hasOpeningParen and hasClosingParen to avoid
// having to allocate a temporary object.
var reusablePos = { line: 1, column: 0 };
var nonSpaceExp = /\S/;
function hasOpeningParen(oldPath) {
var oldNode = oldPath.getValue();
var loc = oldNode.loc;
var lines = loc && loc.lines;
if (lines) {
var pos = reusablePos;
pos.line = loc.start.line;
pos.column = loc.start.column;
while (lines.prevPos(pos)) {
var ch = lines.charAt(pos);
if (ch === "(") {
// If we found an opening parenthesis but it occurred before the
// start of the original subtree for this reprinting, then we must
// not return true for hasOpeningParen(oldPath).
return comparePos(oldPath.getRootValue().loc.start, pos) <= 0;
}
if (nonSpaceExp.test(ch)) {
return false;
}
}
}
return false;
}
function hasClosingParen(oldPath) {
var oldNode = oldPath.getValue();
var loc = oldNode.loc;
var lines = loc && loc.lines;
if (lines) {
var pos = reusablePos;
pos.line = loc.end.line;
pos.column = loc.end.column;
do {
var ch = lines.charAt(pos);
if (ch === ")") {
// If we found a closing parenthesis but it occurred after the end
// of the original subtree for this reprinting, then we must not
// return true for hasClosingParen(oldPath).
return comparePos(pos, oldPath.getRootValue().loc.end) <= 0;
}
if (nonSpaceExp.test(ch)) {
return false;
}
} while (lines.nextPos(pos));
}
return false;
}
function hasParens(oldPath) {
// This logic can technically be fooled if the node has parentheses but
// there are comments intervening between the parentheses and the
// node. In such cases the node will be harmlessly wrapped in an
// additional layer of parentheses.
return hasOpeningParen(oldPath) && hasClosingParen(oldPath);
}
function findChildReprints(newPath, oldPath, reprints) {

@@ -493,25 +433,7 @@ var newNode = newPath.getValue();

// If this type of node cannot come lexically first in its enclosing
// statement (e.g. a function expression or object literal), and it
// seems to be doing so, then the only way we can ignore this problem
// and save ourselves from falling back to the pretty printer is if an
// opening parenthesis happens to precede the node. For example,
// (function(){ ... }()); does not need to be reprinted, even though the
// FunctionExpression comes lexically first in the enclosing
// ExpressionStatement and fails the hasParens test, because the parent
// CallExpression passes the hasParens test. If we relied on the
// path.needsParens() && !hasParens(oldNode) check below, the absence of
// a closing parenthesis after the FunctionExpression would trigger
// pretty-printing unnecessarily.
if (Node.check(newNode) &&
!newPath.canBeFirstInStatement() &&
newPath.firstInStatement() &&
!hasOpeningParen(oldPath)) {
return false;
}
// If this node needs parentheses and will not be wrapped with
// parentheses when reprinted, then return false to skip reprinting and
// let it be printed generically.
if (newPath.needsParens(true) && !hasParens(oldPath)) {
if (newPath.needsParens() &&
! oldPath.hasParens()) {
return false;

@@ -518,0 +440,0 @@ }

{
"author": "Ben Newman <bn@cs.stanford.edu>",
"name": "recast",
"version": "0.16.0",
"description": "JavaScript syntax tree transformer, nondestructive pretty-printer, and automatic source map generator",

@@ -15,3 +16,2 @@ "keywords": [

],
"version": "0.15.5",
"homepage": "http://github.com/benjamn/recast",

@@ -32,3 +32,3 @@ "repository": {

"dependencies": {
"ast-types": "0.11.5",
"ast-types": "0.11.6",
"esprima": "~4.0.0",

@@ -43,6 +43,6 @@ "private": "~0.1.5",

"esprima-fb": "^15001.1001.0-dev-harmony-fb",
"flow-parser": "^0.80.0",
"flow-parser": "^0.83.0",
"glob": "^7.1.2",
"mocha": "~5.2.0",
"reify": "^0.17.3"
"reify": "^0.18.0"
},

@@ -49,0 +49,0 @@ "engines": {

"use strict";
// Prefer the new @babel/parser package, but fall back to babylon if
// that's what's available.
const parser = exports.parser = function () {
try {
return require("@babel/parser");
} catch (e) {
return require("babylon");
}
}();
// This module is suitable for passing as options.parser when calling
// recast.parse to process JavaScript code with Babel:
//
// const ast = recast.parse(source, {
// parser: require("recast/parsers/babylon")
// });
//
exports.parse = function (source, options) {
options = require("./_babylon_options.js")(options);
options.plugins.push("jsx", "flow");
return parser.parse(source, options);
};
Object.assign(exports, require("./babel.js"));

@@ -21,3 +21,3 @@ "use strict";

tolerant: getOption(options, "tolerant", true),
tokens: getOption(options, "tokens", true)
tokens: true
});

@@ -24,0 +24,0 @@

"use strict";
const parser = require("./babylon.js").parser;
const parser = require("./babel.js").parser;

@@ -13,5 +13,5 @@ // This module is suitable for passing as options.parser when calling

exports.parse = function parse(source, options) {
options = require("./_babylon_options.js")(options);
options = require("./_babel_options.js")(options);
options.plugins.push("jsx", "flow");
return parser.parse(source, options);
};
"use strict";
const parser = require("./babylon.js").parser;
const parser = require("./babel.js").parser;

@@ -13,5 +13,5 @@ // This module is suitable for passing as options.parser when calling

exports.parse = function parse(source, options) {
options = require("./_babylon_options.js")(options);
options = require("./_babel_options.js")(options);
options.plugins.push("typescript");
return parser.parse(source, options);
};

@@ -45,3 +45,3 @@ # recast, _v_. [![Build Status](https://travis-ci.org/benjamn/recast.svg?branch=master)](https://travis-ci.org/benjamn/recast) [![Join the chat at https://gitter.im/benjamn/recast](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/benjamn/recast?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Greenkeeper badge](https://badges.greenkeeper.io/benjamn/recast.svg)](https://greenkeeper.io/)

See [ast-types](https://github.com/benjamn/ast-types) (especially the [def/core.js](https://github.com/benjamn/ast-types/blob/master/def/core.js)) module for a thorough overview of the `ast` api.
See [ast-types](https://github.com/benjamn/ast-types) (especially the [def/core.js](https://github.com/benjamn/ast-types/blob/master/def/core.js)) module for a thorough overview of the `ast` API.
```js

@@ -48,0 +48,0 @@ // Grab a reference to the function declaration we just parsed.

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc