Skip to content

Commit 3d7f8a7

Browse files
author
codeclimate-shipbot
committed
Merge pull request #60 from codeclimate/will/complexity
Mercy merged on behalf of will.
2 parents 49c6a0d + 8f6fcab commit 3d7f8a7

File tree

8 files changed

+103
-13
lines changed

8 files changed

+103
-13
lines changed

Diff for: .codeclimate.yml

+1
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ ratings:
1212
- lib/checks.js"
1313
exclude_paths:
1414
- "node_modules/**"
15+
- "test/**"

Diff for: .eslintrc

+2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
"brace-style": [2, "1tbs", { "allowSingleLine": true }],
77
"comma-dangle": [2, "never"],
88
"comma-style": [2, "first", { exceptions: {ArrayExpression: true, ObjectExpression: true} }],
9+
"complexity": [2, 6],
910
"curly": 2,
1011
"eqeqeq": [2, "allow-null"],
12+
"max-statements": [2, 30],
1113
"no-shadow-restricted-names": 2,
1214
"no-undef": 2,
1315
"no-use-before-define": 2,

Diff for: Makefile

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
.PHONY: image
1+
.PHONY: image test
22

33
IMAGE_NAME ?= codeclimate/codeclimate-eslint
44

55
image:
66
docker build --rm -t $(IMAGE_NAME) .
7+
8+
test: image
9+
docker run $(IMAGE_NAME) npm run test

Diff for: bin/eslint.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ function buildIssueJson(message, path) {
6060
}
6161
}
6262
},
63-
remediation_points: checks.remediationPoints(checkName, message)
63+
remediation_points: checks.remediationPoints(checkName, message, cli.getConfigForFile(path))
6464
};
6565
return JSON.stringify(issue);
6666
}

Diff for: circle.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ machine:
1111

1212
test:
1313
override:
14-
- docker build -t=$PRIVATE_REGISTRY/$CIRCLE_PROJECT_REPONAME:b$CIRCLE_BUILD_NUM .
14+
- IMAGE_NAME="$PRIVATE_REGISTRY/$CIRCLE_PROJECT_REPONAME:b$CIRCLE_BUILD_NUM" make test
1515

1616
deployment:
1717
registry:

Diff for: lib/checks.js

+38-10
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ var checkCategoryMapping = {
1414
"guard-for-in": "Bug Risk",
1515
"handle-callback-err": "Bug Risk",
1616
"init-declarations": "Clarity",
17+
"max-statements": "Complexity",
1718
"no-alert": "Bug Risk",
1819
"no-caller": "Compatibility",
1920
"no-case-declarations": "Bug Risk",
@@ -113,21 +114,48 @@ var categories = function(checkName) {
113114
return [checkCategoryMapping[checkName] || "Style"];
114115
};
115116

116-
var remediationPoints = function(checkName, message) {
117-
if (checkName === "complexity") {
117+
// Here Be Dragons: this function extracts the relevant value that triggered the issue for
118+
// checks in the Complexity category. Unfortunately, these values are not available in a
119+
// structured way, so we extract them from strings. That means that any check categorized
120+
// as Complexity MUST have a rule here to extract value.
121+
//
122+
// If a matching string segment cannot be found, `null` will be returned.
123+
var messageMetricValue = function(message) {
124+
var match = null;
125+
switch (message.ruleId) {
126+
case "complexity":
127+
match = message.message.match(/complexity of (\d+)/);
128+
break;
129+
case "max-statements":
130+
match = message.message.match(/too many statements \((\d+)\)/);
131+
break;
132+
}
133+
if (null !== match) {
134+
return parseInt(match[1], 10);
135+
}
136+
return null;
137+
};
138+
139+
var metricThreshold = function(ruleId, eslintConfig) {
140+
return eslintConfig.rules[ruleId][1];
141+
};
142+
143+
var remediationPoints = function(checkName, message, eslintConfig) {
144+
if (categories(checkName)[0] === "Complexity") {
118145
// (@base_cost + (overage * @cost_per))*1_000_000
119146
// cost_per: 0.1, base: 1
120-
var costPer = 100000
147+
var costPer = 70000
121148
, points = 1000000
122-
, threshold = 10 // TODO: Get this from the eslintrc
149+
, threshold = metricThreshold(message.ruleId, eslintConfig)
123150
, overage
124-
, complexity;
125-
126-
complexity = message.message.match(/complexity of (\d+)/)[1];
127-
overage = complexity - threshold;
151+
, metricValue;
128152

129-
if (overage > 0) {
130-
points += (costPer * overage);
153+
metricValue = messageMetricValue(message);
154+
if (null !== metricValue) {
155+
overage = metricValue - threshold;
156+
if (overage > 0) {
157+
points += (costPer * overage);
158+
}
131159
}
132160

133161
return points;

Diff for: package.json

+7
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,12 @@
1515
"eslint-plugin-react": "3.6.3",
1616
"glob": "5.0.14"
1717
},
18+
"devDependencies": {
19+
"chai": "^2.1.0",
20+
"mocha": "^2.3.4"
21+
},
22+
"scripts": {
23+
"test": "mocha test"
24+
},
1825
"engine": "node >= 0.12.4"
1926
}

Diff for: test/checks_test.js

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
var checks = require("../lib/checks");
2+
var expect = require("chai").expect;
3+
4+
describe("checks module", function() {
5+
describe(".categories()", function() {
6+
it("returns complexity for max-statements", function() {
7+
expect(checks.categories("max-statements")).to.deep.eq(["Complexity"]);
8+
});
9+
10+
it("returns style for an unknown check type", function() {
11+
expect(checks.categories("floofle")).to.deep.eq(["Style"]);
12+
});
13+
});
14+
15+
describe(".remediationPoints()", function() {
16+
it("returns the default of 50,000 for a non-complexity issue", function() {
17+
var issue = { ruleId: "eqeqeq", message: "always use ==="};
18+
expect(checks.remediationPoints(issue.ruleId, issue, null)).to.eq(50000);
19+
});
20+
21+
it("calculates the cost for a cyclomatic complexity issue", function() {
22+
var issue = { ruleId: "complexity", message: "Function 'complex' has a complexity of 8." }
23+
, config = eslintConfig({ "complexity": [2, 6] });
24+
expect(checks.remediationPoints(issue.ruleId, issue, config)).to.eq(1140000);
25+
});
26+
27+
it("calculates the cost for a max-statements issue", function() {
28+
var issue = { ruleId: "max-statements", message: "This function has too many statements (38). Maximum allowed is 30." }
29+
, config = eslintConfig({ "max-statements": [2, 30] });
30+
expect(checks.remediationPoints(issue.ruleId, issue, config)).to.eq(1560000);
31+
});
32+
33+
it("uses base complexity cost if metric cannot be found", function() {
34+
var issue = { ruleId: "complexity", message: "has a complexity of \"8\"" }
35+
, config = eslintConfig({ "complexity": [2, 6] });
36+
expect(checks.remediationPoints(issue.ruleId, issue, config)).to.eq(1000000);
37+
});
38+
39+
it("uses base complexity cost if overage is negative somehow", function() {
40+
var issue = { ruleId: "complexity", message: "has a complexity of 8" }
41+
, config = eslintConfig({ "complexity": [2, 10] });
42+
expect(checks.remediationPoints(issue.ruleId, issue, config)).to.eq(1000000);
43+
});
44+
45+
var eslintConfig = function(rulesConfig) {
46+
return { rules: rulesConfig };
47+
};
48+
});
49+
});

0 commit comments

Comments
 (0)