Skip to content

Commit 9a38961

Browse files
fix: ignore some nodes in type coverage (#28)
1 parent 064e6fb commit 9a38961

1 file changed

Lines changed: 139 additions & 44 deletions

File tree

type-coverage/index.js

Lines changed: 139 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -45,62 +45,157 @@ for (const sourceFile of program.getSourceFiles()) {
4545
coverageReport[rep.path] = rep;
4646
let statementIndex = 0;
4747

48+
/**
49+
* @param {ts.Node} node the node to be walked
50+
* @returns {boolean}
51+
*/
52+
const isReplaceMethod = (node) => {
53+
if (!ts.isIdentifier(node)) {
54+
return false;
55+
}
56+
57+
if (node.text !== "replace") {
58+
return false;
59+
}
60+
61+
if (!ts.isPropertyAccessExpression(node.parent)) {
62+
return false;
63+
}
64+
65+
if (node.parent.name !== node) {
66+
return false;
67+
}
68+
69+
const callExpr = node.parent.parent;
70+
71+
if (!ts.isCallExpression(callExpr)) {
72+
return false;
73+
}
74+
75+
const objType = typeChecker.getTypeAtLocation(node.parent.expression);
76+
const isString =
77+
(objType.getFlags() &
78+
(ts.TypeFlags.String | ts.TypeFlags.StringLiteral)) !==
79+
0;
80+
81+
if (!isString) {
82+
return false;
83+
}
84+
85+
const args = callExpr.arguments;
86+
87+
if (args.length < 2) {
88+
return false;
89+
}
90+
91+
const secondArg = args[1];
92+
const isStringOrTemplate =
93+
// "string"
94+
ts.isStringLiteral(secondArg) ||
95+
// `template`
96+
ts.isNoSubstitutionTemplateLiteral(secondArg) ||
97+
// `template ${var}`
98+
ts.isTemplateExpression(secondArg);
99+
100+
if (isStringOrTemplate) {
101+
return true;
102+
}
103+
104+
const type = typeChecker.getTypeAtLocation(secondArg);
105+
const signatures = type.getCallSignatures();
106+
107+
let needIgnore = false;
108+
109+
for (const signature of signatures) {
110+
const parameters = signature.getParameters();
111+
112+
if (parameters.length === 1) {
113+
needIgnore = true;
114+
}
115+
}
116+
117+
return needIgnore;
118+
};
119+
48120
/**
49121
* @param {ts.Node} node the node to be walked
50122
* @returns {void}
51123
*/
52124
const walkNode = (node) => {
53125
if (ts.isIdentifier(node) || node.kind === ts.SyntaxKind.ThisKeyword) {
54126
const type = typeChecker.getTypeAtLocation(node);
55-
if (type) {
56-
const { line, character } = ts.getLineAndCharacterOfPosition(
57-
sourceFile,
58-
node.getStart(),
59-
);
60-
const { line: lineEnd, character: characterEnd } =
61-
ts.getLineAndCharacterOfPosition(sourceFile, node.getEnd());
62-
const typeText = typeChecker.typeToString(type);
63-
let isExternal = false;
64-
65-
/**
66-
* @param {ts.Type} type the type to be checked
67-
* @returns {void}
68-
*/
69-
const checkDecls = (type) => {
70-
if (!type.symbol) return;
71-
for (const decl of type.symbol.getDeclarations()) {
72-
const sourceFile = decl.getSourceFile();
73-
if (sourceFile && sourceFile.isDeclarationFile) isExternal = true;
74-
}
75-
};
76-
if (node.parent && ts.isPropertyAccessExpression(node.parent)) {
77-
const expressionType = typeChecker.getTypeAtLocation(
78-
node.parent.expression,
79-
);
80-
checkDecls(expressionType);
81-
}
82-
if (/^(<.*>)?\(/.test(typeText)) {
83-
checkDecls(type);
127+
128+
if (!type) {
129+
return;
130+
}
131+
132+
const { line, character } = ts.getLineAndCharacterOfPosition(
133+
sourceFile,
134+
node.getStart(),
135+
);
136+
const { line: lineEnd, character: characterEnd } =
137+
ts.getLineAndCharacterOfPosition(sourceFile, node.getEnd());
138+
139+
let isExternal = false;
140+
141+
/**
142+
* @param {ts.Type} type the type to be checked
143+
* @returns {void}
144+
*/
145+
const checkDecls = (type) => {
146+
if (!type.symbol) return;
147+
for (const decl of type.symbol.getDeclarations()) {
148+
const sourceFile = decl.getSourceFile();
149+
if (sourceFile && sourceFile.isDeclarationFile) isExternal = true;
84150
}
85-
const isTyped =
86-
isExternal ||
87-
(!(type.flags & ts.TypeFlags.Any) && !/\bany\b/.test(typeText));
88-
rep.statementMap[statementIndex] = {
89-
start: {
90-
line: line + 1,
91-
column: character,
92-
},
93-
end: {
94-
line: lineEnd + 1,
95-
column: characterEnd - 1,
96-
},
97-
};
98-
rep.s[statementIndex] = isTyped ? typeText.length : 0;
99-
statementIndex++;
151+
};
152+
153+
if (node.parent && ts.isPropertyAccessExpression(node.parent)) {
154+
const expressionType = typeChecker.getTypeAtLocation(
155+
node.parent.expression,
156+
);
157+
checkDecls(expressionType);
100158
}
159+
160+
const typeText = typeChecker.typeToString(type);
161+
162+
if (/^(<.*>)?\(/.test(typeText)) {
163+
checkDecls(type);
164+
}
165+
166+
const isTyped =
167+
// Ignore labeled statements
168+
((ts.isLabeledStatement(node.parent) ||
169+
ts.isBreakStatement(node.parent) ||
170+
ts.isContinueStatement(node.parent)) &&
171+
node.parent.label === node) ||
172+
// Ignore `name` in `const { name: otherName } = obj;`
173+
(ts.isBindingElement(node.parent) &&
174+
node.parent.propertyName === node) ||
175+
// Ignore `"string".replace("s", "t")`, because the second argument can be a function with `any` types
176+
isReplaceMethod(node) ||
177+
// Ignore external types
178+
isExternal ||
179+
// Should be not `any` and don't include `any` in types
180+
(!(type.flags & ts.TypeFlags.Any) && !/\bany\b/.test(typeText));
181+
182+
rep.statementMap[statementIndex] = {
183+
start: {
184+
line: line + 1,
185+
column: character,
186+
},
187+
end: {
188+
line: lineEnd + 1,
189+
column: characterEnd - 1,
190+
},
191+
};
192+
rep.s[statementIndex] = isTyped ? typeText.length : 0;
193+
statementIndex++;
101194
}
195+
102196
node.forEachChild(walkNode);
103197
};
198+
104199
sourceFile.forEachChild(walkNode);
105200
}
106201
}

0 commit comments

Comments
 (0)