Comparing version 0.0.0-pre.1 to 0.0.0
{ | ||
"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 | ||
} | ||
} |
191
README.md
@@ -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 <rules>, -r=<rules> | Array of String | Rule, or group: all, logic, raw. Default: logic | | ||
| --format <format>, -f=<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 <value>, -gt=<value> | String or Number | Will show rules more than rank a, b, c, d, e, or rank value | | ||
| --less-than <value>, -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 |
'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; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
No website
QualityPackage does not have a website.
Found 1 instance in 1 package
31120
654
1
195
2
1
1
+ Added@ndk/env@^0.0.3
Updatedeslint@^5.10.0