Skip to content

Commit c7669cc

Browse files
committed
add type coverage report tool
1 parent b8c47a7 commit c7669cc

File tree

3 files changed

+115
-3
lines changed

3 files changed

+115
-3
lines changed

codecov.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ coverage:
1818
target: 0%
1919
patch:
2020
default: off
21-
integration:
22-
flags: integration
21+
basic:
22+
flags: basic
2323
target: 90%
2424
base: pr
2525
integration:

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@
118118
"test:basic": "node --max-old-space-size=4096 --trace-deprecation node_modules/jest-cli/bin/jest --testMatch \"<rootDir>/test/{TestCasesNormal,StatsTestCases,ConfigTestCases}.test.js\"",
119119
"test:unit": "node --max-old-space-size=4096 --trace-deprecation node_modules/jest-cli/bin/jest --testMatch \"<rootDir>/test/*.unittest.js\"",
120120
"travis:integration": "yarn cover:init && yarn cover:integration \"test/(?!TestCases)\" --ci $JEST && mv coverage/coverage-final.json coverage/coverage-final-1.json && yarn cover:integration \"test/TestCasesD\" --ci $JEST && mv coverage/coverage-final.json coverage/coverage-final-2.json && yarn cover:integration \"test/TestCases(?!D)\" --ci $JEST && mv coverage/coverage-final.json coverage/coverage-final-3.json",
121-
"travis:basic": "yarn test:basic --ci $JEST",
121+
"travis:basic": "yarn test:basic --ci $JEST && yarn cover:init && yarn run cover:types",
122122
"travis:lint-unit": "yarn lint && yarn cover:init && yarn cover:unit --ci $JEST",
123123
"travis:benchmark": "yarn benchmark --ci",
124124
"appveyor:integration": "yarn cover:init && yarn cover:integration \"test/(?!TestCases)\" --ci %JEST% && move coverage\\coverage-final.json coverage\\coverage-final-1.json&& yarn cover:integration \"test/TestCasesD\" --ci %JEST% && move coverage\\coverage-final.json coverage\\coverage-final-2.json && yarn cover:integration \"test/TestCases(?!D)\" --ci %JEST% && move coverage\\coverage-final.json coverage\\coverage-final-3.json",
@@ -139,6 +139,7 @@
139139
"cover:all": "node --max-old-space-size=4096 node_modules/jest-cli/bin/jest --coverage",
140140
"cover:integration": "node --max-old-space-size=4096 node_modules/jest-cli/bin/jest --testMatch \"<rootDir>/test/*.test.js\" --coverage",
141141
"cover:unit": "node --max-old-space-size=4096 node_modules/jest-cli/bin/jest --testMatch \"<rootDir>/test/*.unittest.js\" --coverage",
142+
"cover:types": "yarn run type-lint && node tooling/type-coverage.js",
142143
"cover:report": "istanbul report"
143144
},
144145
"husky": {

tooling/type-coverage.js

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// loosly based on https://github.com/plantain-00/type-coverage
2+
3+
const path = require("path");
4+
const fs = require("fs");
5+
const mkdirp = require("mkdirp");
6+
const ts = require("typescript");
7+
const program = require("./typescript-program");
8+
9+
const typeChecker = program.getTypeChecker();
10+
11+
const projectPaths = [
12+
path.resolve(__dirname, "../lib"),
13+
path.resolve(__dirname, "../bin"),
14+
path.resolve(__dirname, "../tooling"),
15+
path.resolve(__dirname, "../declarations.d.ts")
16+
];
17+
const basePath = path.resolve(__dirname, "..");
18+
19+
const isProjectFile = file => {
20+
return projectPaths.some(p =>
21+
file.toLowerCase().startsWith(p.replace(/\\/g, "/").toLowerCase())
22+
);
23+
};
24+
25+
const coverageReport = Object.create(null);
26+
27+
for (const sourceFile of program.getSourceFiles()) {
28+
let file = sourceFile.fileName;
29+
if (isProjectFile(file)) {
30+
const rep = {
31+
path: file,
32+
statementMap: {},
33+
fnMap: {},
34+
branchMap: {},
35+
s: {},
36+
f: {},
37+
b: {}
38+
};
39+
coverageReport[file] = rep;
40+
let statementIndex = 0;
41+
42+
/**
43+
* @param {ts.Node} node the node to be walked
44+
* @returns {void}
45+
*/
46+
const walkNode = node => {
47+
if (ts.isIdentifier(node) || node.kind === ts.SyntaxKind.ThisKeyword) {
48+
const type = typeChecker.getTypeAtLocation(node);
49+
if (type) {
50+
const { line, character } = ts.getLineAndCharacterOfPosition(
51+
sourceFile,
52+
node.getStart()
53+
);
54+
const {
55+
line: lineEnd,
56+
character: characterEnd
57+
} = ts.getLineAndCharacterOfPosition(sourceFile, node.getEnd());
58+
const typeText = typeChecker.typeToString(type);
59+
let isExternal = false;
60+
61+
/**
62+
* @param {ts.Type} type the type to be checked
63+
* @returns {void}
64+
*/
65+
const checkDecls = type => {
66+
if (!type.symbol) return;
67+
for (const decl of type.symbol.getDeclarations()) {
68+
const sourceFile = decl.getSourceFile();
69+
if (sourceFile && !isProjectFile(sourceFile.fileName))
70+
isExternal = true;
71+
}
72+
};
73+
if (node.parent && ts.isPropertyAccessExpression(node.parent)) {
74+
const expressionType = typeChecker.getTypeAtLocation(
75+
node.parent.expression
76+
);
77+
checkDecls(expressionType);
78+
}
79+
if (/^(<.*>)?\(/.test(typeText)) {
80+
checkDecls(type);
81+
}
82+
const isTyped =
83+
isExternal ||
84+
(!(type.flags & ts.TypeFlags.Any) && !/\bany\b/.test(typeText));
85+
rep.statementMap[statementIndex] = {
86+
start: {
87+
line: line + 1,
88+
column: character
89+
},
90+
end: {
91+
line: lineEnd + 1,
92+
column: characterEnd - 1
93+
}
94+
};
95+
rep.s[statementIndex] = isTyped ? 1 : 0;
96+
statementIndex++;
97+
}
98+
}
99+
node.forEachChild(walkNode);
100+
};
101+
sourceFile.forEachChild(walkNode);
102+
}
103+
}
104+
105+
const outputDirectory = path.resolve(__dirname, "../coverage");
106+
mkdirp.sync(outputDirectory);
107+
fs.writeFileSync(
108+
path.resolve(outputDirectory, "coverage-types.json"),
109+
JSON.stringify(coverageReport),
110+
"utf-8"
111+
);

0 commit comments

Comments
 (0)