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

eslintcc

Package Overview
Dependencies
Maintainers
1
Versions
76
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

eslintcc - npm Package Compare versions

Comparing version 0.0.0-pre.1 to 0.0.0

source/lib/eslint-patches.js

31

package.json
{
"name": "eslintcc",
"version": "0.0.0-pre.1",
"description": "ESLintCC is a JavaScript tool that computes cyclomatic complexity by using ESLint",
"repository": "github:IgorNovozhilov/eslintcc",
"version": "0.0.0",
"homepage": "https://github.com/eslintcc/eslintcc",
"description": "ESLintCC is a ECMAScript/JavaScript tool that computes cyclomatic complexity by using ESLint",
"keywords": [
"eslint",
"javascript",
"linter",
"static-code-analysis",
"cyclomatic-complexity"
],
"repository": "github:eslintcc/eslintcc",
"license": "MIT",
"main": "./source/cli.js",
"main": "./source/complexity.js",
"bin": {

@@ -16,4 +24,17 @@ "eslintcc": "eslintcc"

"dependencies": {
"eslint": "^5.7.0"
"eslint": "^5.10.0",
"@ndk/env": "^0.0.3"
},
"devDependencies": {
"@ndk/test": "^0.0.4"
},
"scripts": {
"test": "node test",
"coverage": "nyc node test && nyc report --reporter=html",
"coverage-all": "nyc --exclude=!test --exclude=coverage node test && nyc report --reporter=html",
"travis-build": "eslint . --ignore-pattern /test/src/ && nyc --reporter=lcovonly node test && nyc report && coveralls < coverage/lcov.info"
},
"nyc": {
"all": true
}
}

@@ -1,9 +0,194 @@

# ESLint Cyclomatic Complexity Tool [![npm][npm_img]][npm_url]
# ESLint Cyclomatic Complexity Tool [![npm][npm_img]][npm_url] [![Build Status][build_img]][build_url] [![Coverage Status][coverage_img]][coverage_url]
ESLintCC is a JavaScript tool that computes cyclomatic complexity by using ESLint
[ESLintCC][npm_url] is a ECMAScript/JavaScript tool
that computes cyclomatic complexity by using [ESLint][eslint_npm]
> While in development :relaxed:
> ESLint calculates cyclomatic complexity,
> while this tool only collects a report based on his [complexity rule messages][eslint_rule]
## Installation and Usage
> Requirements, principles of local and global installation and usage
> are the same as [ESLint Installation and Usage][eslint_usage]
Globally:
$ npm install -g eslintcc
$ eslintcc yourfile.js
Locally:
$ npm install eslintcc
$ ./node_modules/.bin/eslintcc yourfile.js
Integration in JavaScript application:
```js
const { Complexity } = require('eslintcc');
const complexity = new Complexity();
const report = complexity.executeOnFiles(['yourfile.js']);
console.log(JSON.stringify(report, null, '\t'));
```
**Note:** ESLintCC ignores all plugins and rules, specified in configuration files,
and uses to generate a report only [complexity rules][eslint_rule].
So there is no need to install plugins dependencies for use ESLintCC.
But, if using a [shareable configuration package][share_conf],
you use must also be installed locally or globally to work with a locally or globally installed ESLintCC.
## Configuration
ESLintCC uses ESLint along with [Its configuration system][eslint_config].
You can use configuration comments and files, as described in the configuration for ESLint.
**Difference:** ESLintCC uses its own settings for complexity rules,
so they cannot be overridden through a configuration file.
However, you can disable them locally in the file.
**Features:**
1. You can configurate [parserOptions][eslint_parser_options]
and [parser][eslint_parser] for specify the JavaScript language support. `.eslintrc.json`:
```json
{
"parserOptions": {
"ecmaVersion": 2017
}
}
```
2. You can disable checks for a specific complexity rule for a file or part of file
[using a comment][eslint_disabling_comments]:
```js
// For a file
/* eslint max-params: off, max-depth: off */
function myFunc(a, b, c, d, e) {
//...
}
```
```js
// For a block
/* eslint-disable max-params */
function myFunc(a, b, c, d, e) {
//...
}
/* eslint-enable max-params */
function myFunc2(a, b) {
//...
}
```
```js
// For a line
/* eslint-disable-next-line max-params */
function myFunc(a, b, c, d, e) {
//...
}
```
## Complexity ranks
Every function and block will be ranked from A (best complexity score) to F (worst one).
This ranks is based on the ranks of complexity of the [Python Radon][radon_cc_rank].
**Rank Risk**
- **A** low - simple block
- **B** low - well structured and stable block
- **C** moderate - slightly complex block
- **D** more than moderate - more complex block
- **E** high - complex block, alarming
- **F** very high - error-prone, unstable block
Ranks corresponds to rule complexity scores as follows:
| Rules | A | B | C | D | E | F |
| ----------------------------------------------------------- | ------ | -------- | --------- | --------- | --------- | ----- |
| Logic: | | | | | | |
| [**complexity**][eslint_rule] | 1 - 5 | 6 - 10 | 11 - 20 | 21 - 30 | 31 - 40 | 41 + |
| [**max-depth**][eslint_max_depth] | 1 - 2 | 3 | 4 - 5 | 6 - 7 | 8 | 9 + |
| [**max-nested-callbacks**][eslint_max_nested_callbacks] | 1 - 3 | 4 - 5 | 6 - 10 | 11 - 15 | 16 - 20 | 21 + |
| [**max-params**][eslint_max_params] | 1 | 2 | 3 - 4 | 5 | 6 | 7 + |
| Raw: | | | | | | |
| [**max-lines**][eslint_max_lines] | 1 - 75 | 76 - 150 | 151 - 300 | 301 - 450 | 451 - 600 | 601 + |
| [**max-lines-per-function**][eslint_max_lines_per_function] | 1 - 13 | 14 - 25 | 26 - 50 | 51 - 75 | 76 - 100 | 101 + |
| [**max-statements**][eslint_max_statements] | 1 - 3 | 4 - 5 | 6 - 10 | 11 - 15 | 16 - 20 | 21 + |
> **Note:** For rank "C", the maximum score, using from the standard score of ESLint rules.
> See [complexity rules][eslint_rule].
> Other rules are calculated relative to the values of the "complexity" rule.
>
> Example formula:
> `[5, 10, 20, 30, 40].map(score => Math.round((score / 20) * defaultRuleScoreLimit))`
## Command line options
Command line format:
$ eslintcc [options] file.js [file.js] [dir]
| Option | Type | Description |
| ----------------------------------------- | ---------------- | ----------------------------------------------------------- |
| --rules &lt;rules>, -r=&lt;rules> | Array of String | Rule, or group: all, logic, raw. Default: logic |
| --format &lt;format>, -f=&lt;format> | String | Use a specific output format, text or json. Default: text |
| --show-rules, -sr | Flag | Show rule name and value, if used text format |
| --greater-than &lt;value>, -gt=&lt;value> | String or Number | Will show rules more than rank a, b, c, d, e, or rank value |
| --less-than &lt;value>, -lt=&lt;value> | String or Number | Will show rules less than rank b, c, d, e, f, or rank value |
### Command examples
Output as JSON and show rules more than rank **E**:
$ eslintcc -f=json -gt=e file.js
Use only 2 rules and show rule name:
$ eslintcc --rules complexity --rules max-depth --show-rules file.js
[npm_img]: https://img.shields.io/npm/v/eslintcc.svg
[npm_url]: https://www.npmjs.com/package/eslintcc
[build_img]: https://travis-ci.com/eslintcc/eslintcc.svg?branch=master
[build_url]: https://travis-ci.com/eslintcc/eslintcc
[coverage_img]: https://coveralls.io/repos/github/eslintcc/eslintcc/badge.svg?branch=master
[coverage_url]: https://coveralls.io/github/eslintcc/eslintcc?branch=master
[eslint_npm]: https://www.npmjs.com/package/eslint
[share_conf]: https://eslint.org/docs/user-guide/configuring#using-a-shareable-configuration-package
[eslint_rule]: https://eslint.org/docs/rules/complexity
[eslint_max_depth]: https://eslint.org/docs/rules/max-depth
[eslint_max_lines]: https://eslint.org/docs/rules/max-lines
[eslint_max_lines_per_function]: https://eslint.org/docs/rules/max-lines-per-function
[eslint_max_nested_callbacks]: https://eslint.org/docs/rules/max-nested-callbacks
[eslint_max_params]: https://eslint.org/docs/rules/max-params
[eslint_max_statements]: https://eslint.org/docs/rules/max-statements
[eslint_usage]: https://github.com/eslint/eslint#installation-and-usage
[eslint_config]: https://eslint.org/docs/user-guide/configuring
[eslint_parser_options]: https://eslint.org/docs/user-guide/configuring#specifying-parser-options
[eslint_parser]: https://eslint.org/docs/user-guide/configuring#specifying-parser
[eslint_disabling_comments]: https://eslint.org/docs/user-guide/configuring#disabling-rules-with-inline-comments
[radon_cc_rank]: https://radon.readthedocs.io/en/latest/api.html#radon.complexity.cc_rank

38

source/cli.js
'use strict';
const { getProcessArgs } = require('@ndk/env/args');
const { Complexity } = require('./complexity');
const { ReportLogger } = require('./logging');
const { ReportLogger } = require('./lib/logging');
const processArgs = getProcessArgs({
types: {
rules: 'Array',
format: 'Option',
showRules: 'Flag',
greaterThan: 'Option',
lessThan: 'Option'
},
aliases: {
rules: 'r',
format: 'f',
showRules: ['show-rules', 'sr'],
greaterThan: ['greater-than', 'gt'],
lessThan: ['less-than', 'lt']
}
});
function parseArgs() {
const options = {};
const args = process.argv.slice(2);
const gt = parseInt(args[args.indexOf('-gt') + 1]);
if (!isNaN(gt)) {
options.complexity = gt;
}
return options;
if (processArgs.argv.length > 0) {
const options = Object.assign({}, processArgs.flags, processArgs.options);
const complexity = new Complexity(options);
new ReportLogger(complexity, options);
complexity.executeOnFiles(processArgs.argv);
} else {
console.log(require('./lib/help'));
}
new ReportLogger(new Complexity(parseArgs()).executeOnFiles(['.'])).log();
'use strict';
const { CLIEngine } = require('eslint');
const EventEmitter = require('events');
const { purifyESLintConfigRules } = require('./lib/config');
const { patchComplexityRule } = require('./lib/complexity-rule');
const { resolveRanks, resolveRankLabel } = require('./lib/rank');
const { patchingESLint, PatchedCLIEngine } = require('./lib/eslint-patches');
const { Ranks } = require('./lib/rank');
const defaultComplexity = 0;
const allComplexityRules = {
'complexity': ['error', 0],
'max-depth': ['error', 0],
//'max-len': ['error', 1], // TODO: https://github.com/IgorNovozhilov/eslintcc/issues/1
'max-lines': ['error', 0],
'max-lines-per-function': ['error', { max: 0 }],
'max-nested-callbacks': ['error', 0],
'max-params': ['error', 0],
'max-statements': ['error', 0]
};
const ruleCategories = {
all: allComplexityRules,
logic: {
'complexity': allComplexityRules['complexity'],
'max-depth': allComplexityRules['max-depth'],
'max-nested-callbacks': allComplexityRules['max-nested-callbacks'],
'max-params': allComplexityRules['max-params']
},
raw: {
//'max-len': allComplexityRules['max-len'],
'max-lines': allComplexityRules['max-lines'],
'max-lines-per-function': allComplexityRules['max-lines-per-function'],
'max-statements': allComplexityRules['max-statements']
}
};
const ruleTypes = {
'complexity': 'function',
'max-depth': 'block',
//'max-len': 'line',
'max-lines': 'file',
'max-lines-per-function': 'function',
'max-nested-callbacks': 'function',
'max-params': 'function',
'max-statements': 'function'
};
// Patching ESLint behavior, for use as a metrics generator
patchingESLint();
// Setup hook for cleaning user-defined rules, because used only complexity rule
purifyESLintConfigRules();
// Patch a cyclomatic complexity rule for more usable for analyze
patchComplexityRule();
class ComplexityFileReportMessage {
static getID(node) {
const { start, end } = node.loc;
return `${start.line}:${start.column}:${end.line}:${end.column}`;
}
static resolveNodeName(node, recursiveUp = false) {
if (node === null) {
return null;
}
const parent = node.parent;
const nameWithParent = (name, separator = ', ') => {
const parentName = this.resolveNodeName(parent, true);
return parentName ? (parentName + separator + name) : name;
};
switch (node.type) {
case 'FunctionExpression':
case 'FunctionDeclaration':
if (!node.id && (recursiveUp || node.loc.start.line === parent.loc.start.line)) {
return this.resolveNodeName(parent, true) || (recursiveUp ? '' : 'function anonymous');
} else {
return nameWithParent('function ' + ((node.id || {}).name || 'anonymous'));
}
case 'MethodDefinition':
return nameWithParent(node.key.name || node.key.raw, node.static ? '.' : '#');
case 'ClassDeclaration':
return nameWithParent('class ' + node.id.name);
case 'VariableDeclarator':
return nameWithParent('variable ' + node.id.name);
case 'Property':
if (node.method || node.value && !node.value.id && node.value.type === 'FunctionExpression') {
return nameWithParent('function ' + (node.key.name || node.key.raw));
}
return this.resolveNodeName(parent, true);
case 'ArrowFunctionExpression':
return nameWithParent(`${node.type}:${node.loc.start.line}-${node.loc.end.line}`);
default:
if (recursiveUp || parent && node.loc.start.line === parent.loc.start.line) {
return this.resolveNodeName(parent, true);
} else {
if (node.loc.start.line === node.loc.end.line) {
return nameWithParent(`${node.type}:${node.loc.start.line}:${node.loc.start.column}`);
} else {
return nameWithParent(`${node.type}:${node.loc.start.line}-${node.loc.end.line}`);
}
}
}
}
static['resolveValue:complexity'](data) {
return data.complexity;
}
static['resolveValue:max-depth'](data) {
return data.depth;
}
static['resolveValue:max-lines'](data) {
return data.actual;
}
static['resolveValue:max-lines-per-function'](data) {
return data.lineCount;
}
static['resolveValue:max-nested-callbacks'](data) {
return data.num;
}
static['resolveValue:max-params'](data) {
return data.count;
}
static['resolveValue:max-statements'](data) {
return data.count;
}
constructor({ messageID, ruleType, node }, { ranks }) {
this.options = { ranks };
this.id = messageID;
this.type = ruleType;
this.loc = node.loc;
this.namePath = this.constructor.resolveNodeName(node);
this.complexityRules = {};
this.complexityRanks = {};
this.maxRuleValue = 0;
this.maxRuleId = null;
this.maxValue = 0;
this.maxLabel = null;
}
toJSON() {
const json = {
id: this.id,
type: this.type,
loc: this.loc,
namePath: this.namePath,
complexityRules: this.complexityRules,
complexityRanks: this.complexityRanks,
maxValue: this.maxValue,
maxLabel: this.maxLabel
};
if (this.errorMessage) {
json.errorMessage = this.errorMessage;
}
return json;
}
pushData(ruleId, data) {
const value = this.constructor[`resolveValue:${ruleId}`](data);
const { rankValue, rankLabel } = this.options.ranks.getValue(ruleId, value);
this.complexityRules[ruleId] = value;
this.complexityRanks[`${ruleId}-value`] = rankValue;
this.complexityRanks[`${ruleId}-label`] = rankLabel;
if (rankValue > this.maxValue) {
this.maxRuleValue = value;
this.maxRuleId = ruleId;
this.maxValue = rankValue;
this.maxLabel = rankLabel;
}
}
pushFatalMessage(ruleId, message) {
const { rankValue, rankLabel } = this.options.ranks.constructor.getMaxValue();
this.complexityRules[ruleId] = 1;
this.complexityRanks[`${ruleId}-value`] = rankValue;
this.complexityRanks[`${ruleId}-label`] = rankLabel;
this.errorMessage = message;
this.maxRuleValue = 1;
this.maxRuleId = ruleId;
this.maxValue = rankValue;
this.maxLabel = rankLabel;
}
}
class ComplexityFileReport {
constructor(fileName, { ranks }) {
this.fileName = fileName;
this.ranks = ranks;
this.messagesTypesMap = { file: {}, function: {}, block: {} };
this.messagesMap = {};
this.messages = [];
}
toJSON() {
return {
fileName: this.fileName,
messages: this.messages
};
}
__pushMessage(messageID, ruleType, node) {
const message = new ComplexityFileReportMessage({ messageID, ruleType, node }, { ranks: this.ranks });
this.messagesTypesMap[ruleType][messageID] = message;
this.messagesMap[messageID] = message;
this.messages.push(message);
return message;
}
pushMessage({ ruleId, ruleType, node, data }) {
node = node || {
loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 0 } },
type: 'Program',
parent: null
};
const messageID = ComplexityFileReportMessage.getID(node);
const reportMessage = this.messagesMap[messageID] || this.__pushMessage(messageID, ruleType, node);
reportMessage.pushData(ruleId, data);
}
pushFatalMessage({ ruleId, ruleType, line, column, message }) {
const loc = { start: { line, column }, end: { line, column } };
const node = { loc, type: 'Program', parent: null };
const messageID = ComplexityFileReportMessage.getID(node);
const reportMessage = this.messagesMap[messageID] || this.__pushMessage(messageID, ruleType, node);
reportMessage.pushFatalMessage(ruleId, message);
}
}
class ComplexityReport {
constructor({ ranks, greaterThan, lessThan }) {
this.options = { ranks, greaterThan, lessThan };
this.events = new EventEmitter();
this.files = [];
}
toJSON() {
return {
files: this.files
};
}
verifyFile(fileName, messages) {
const fileReport = new ComplexityFileReport(fileName, this.options);
messages.forEach(message => {
if (message.fatal) {
message.ruleId = 'fatal-error';
message.ruleType = 'file';
fileReport.pushFatalMessage(message);
} else {
message = message.message;
message.ruleType = ruleTypes[message.ruleId];
fileReport.pushMessage(message);
}
});
const { greaterThan, lessThan } = this.options;
if (typeof greaterThan === 'number' || typeof lessThan === 'number') {
const gt = typeof greaterThan === 'number' ? greaterThan : -Infinity;
const lt = typeof lessThan === 'number' ? lessThan : Infinity;
fileReport.messages = fileReport.messages.filter(message => {
if (message.maxValue <= gt) {
return false;
}
if (message.maxValue > lt) {
return false;
}
return true;
});
}
this.files.push(fileReport);
this.events.emit('verifyFile', fileReport);
}
}
class Complexity {
constructor({ complexity = defaultComplexity, ranks = null } = {}) {
this.ranks = resolveRanks(ranks);
const rules = { complexity: ['error', complexity] };
this.cli = new CLIEngine({ rules });
constructor({
rules = 'logic',
greaterThan = undefined,
lessThan = undefined,
ranks = null
} = {}) {
this.options = {
ranks: new Ranks(ranks),
rules: rules,
greaterThan: Ranks.getLabelMaxValue(greaterThan),
lessThan: Ranks.getLabelMinValue(lessThan),
};
this.events = new EventEmitter();
}
analyzeFileComplexity({ filePath, messages }) {
const fileComplexity = { filePath, complexity: 0, messages: [] };
for (const { column, endColumn, endLine, line, message, nodeType } of messages) {
const { name, complexity, ruleMessage } = JSON.parse(message);
const complexityData = { column, endColumn, endLine, line, nodeType, name, complexity, ruleMessage };
complexityData.complexity = parseInt(complexityData.complexity);
complexityData.rank = resolveRankLabel(complexityData.complexity, this.ranks);
fileComplexity.messages.push(complexityData);
fileComplexity.complexity += complexityData.complexity;
getComplexityRules(customCategory) {
const category = customCategory || this.options.rules;
if (category instanceof Array) {
const rules = {};
for (const ctg of category) {
Object.assign(rules, this.getComplexityRules(ctg));
}
return rules;
} else {
if (category in allComplexityRules) {
return {
[category]: allComplexityRules[category]
};
} else if (category in ruleCategories) {
return ruleCategories[category];
} else {
return ruleCategories['logic'];
}
}
return fileComplexity;
}
executeOnFiles(patterns) {
const report = this.cli.executeOnFiles(patterns).results;
const reportComplexity = { cwd: process.cwd(), complexity: 0, results: [] };
for (const fileReport of report) {
const fileComplexity = this.analyzeFileComplexity(fileReport);
reportComplexity.results.push(fileComplexity);
reportComplexity.complexity += fileComplexity.complexity;
}
return reportComplexity;
const engine = new PatchedCLIEngine({ rules: this.getComplexityRules() });
const report = new ComplexityReport(this.options);
engine.events.on('verifyFile', report.verifyFile.bind(report));
report.events.on('verifyFile', (...args) => this.events.emit('verifyFile', ...args));
engine.executeOnFiles(patterns);
this.events.emit('finish', report);
return report;
}

@@ -50,0 +328,0 @@

'use strict';
// The maximum complexity value associated with the rank
const defaultRanks = {
A: 5,
B: 10,
C: 20,
D: 30,
E: 40,
const rankLabels = ['A', 'B', 'C', 'D', 'E', 'F'];
const rankLabelsMaxValue = {
A: 1,
B: 2,
C: 3,
D: 4,
E: 5,
F: Infinity

@@ -14,18 +14,134 @@ };

function resolveRanks(ranks) {
return Object.assign({}, defaultRanks, ranks || {});
}
class Ranks {
static getLabelMaxValue(label) {
label = String(label).toUpperCase();
const maxValue = rankLabelsMaxValue[label] || Number(label);
if (isNaN(maxValue)) {
return null;
} else {
return maxValue;
}
}
function resolveRankLabel(value, customRanks) {
const maxRanks = customRanks || defaultRanks;
for (const label in maxRanks) {
if (value < maxRanks[label]) {
return label;
static getLabelMinValue(label) {
label = String(label).toUpperCase();
const minValue = rankLabels.includes(label) ? rankLabels.indexOf(label) : Number(label);
if (isNaN(minValue)) {
return null;
} else {
return minValue;
}
}
static getMaxValue() {
return {
rankValue: rankLabelsMaxValue['E'] + 1,
rankLabel: 'F'
};
}
get defaultRanks() {
// The maximum complexity score for rule, associated with the rank
// 'complexity' corresponds https://radon.readthedocs.io/en/latest/api.html#radon.complexity.cc_rank.
// The rest are calculated relative to the default score for the rule.
// Example formula for calculating the score relations:
// [5, 10, 20, 30, 40].map(score => Math.round((score / 20) * defaultRuleScoreLimit))
return {
'complexity': {
A: 5,
B: 10,
C: 20,
D: 30,
E: 40,
F: Infinity
},
'max-depth': {
A: 2,
B: 3,
C: 4,
D: 6,
E: 8,
F: Infinity
},
//'max-len': {},
'max-lines': {
A: 75,
B: 150,
C: 300,
D: 450,
E: 600,
F: Infinity
},
'max-lines-per-function': {
A: 13,
B: 25,
C: 50,
D: 75,
E: 100,
F: Infinity
},
'max-nested-callbacks': {
A: 3,
B: 5,
C: 10,
D: 15,
E: 20,
F: Infinity
},
'max-params': {
A: 1,
B: 2,
C: 3,
D: 5,
E: 6,
F: Infinity
},
'max-statements': {
A: 3,
B: 5,
C: 10,
D: 15,
E: 20,
F: Infinity
}
};
}
constructor(customRulesRanks = {}) {
const ranks = this.defaultRanks;
for (const ruleID in customRulesRanks) {
if (ruleID in ranks) {
const customRanks = customRulesRanks[ruleID];
for (let rankName in customRanks) {
rankName = rankName.toUpperCase();
if (rankName in ranks[ruleID]) {
ranks[ruleID][rankName] = Number(customRanks[rankName]);
}
}
}
}
this.ranks = ranks;
}
getValue(ruleID, value) {
const ranks = this.ranks[ruleID];
for (let i = 0; i < rankLabels.length; i++) {
const rankLabel = rankLabels[i];
const rankMaxValue = ranks[rankLabel];
if (value <= rankMaxValue) {
const prevMaxValue = ranks[rankLabels[i - 1]] || 0;
const range = rankMaxValue === Infinity ? prevMaxValue : rankMaxValue - prevMaxValue;
return {
rankValue: ((i + (value - prevMaxValue) / range) * 1000 ^ 0) / 1000,
rankLabel: rankLabel
};
}
}
}
}
exports.resolveRanks = resolveRanks;
exports.resolveRankLabel = resolveRankLabel;
exports.Ranks = Ranks;
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