@@ -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 ) && ! / \b a n y \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 ) && ! / \b a n y \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