mirror of https://github.com/webpack/webpack.git
				
				
				
			
		
			
				
	
	
		
			134 lines
		
	
	
		
			3.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
			
		
		
	
	
			134 lines
		
	
	
		
			3.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
| // loosly based on https://github.com/plantain-00/type-coverage
 | |
| 
 | |
| const path = require("path");
 | |
| const fs = require("fs");
 | |
| const mkdirp = require("mkdirp");
 | |
| const ts = require("typescript");
 | |
| const program = require("./typescript-program");
 | |
| 
 | |
| const typeChecker = program.getTypeChecker();
 | |
| 
 | |
| const projectPaths = [
 | |
| 	path.resolve(__dirname, "../lib"),
 | |
| 	path.resolve(__dirname, "../bin"),
 | |
| 	path.resolve(__dirname, "../tooling"),
 | |
| 	path.resolve(__dirname, "../declarations.d.ts")
 | |
| ];
 | |
| 
 | |
| /**
 | |
|  * @param {string} file filename
 | |
|  * @returns {boolean} true, when file is part of the project
 | |
|  */
 | |
| const isProjectFile = file => {
 | |
| 	return projectPaths.some(p =>
 | |
| 		file.toLowerCase().startsWith(p.replace(/\\/g, "/").toLowerCase())
 | |
| 	);
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * @typedef {Object} Location
 | |
|  * @property {number} line
 | |
|  * @property {number} column
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * @typedef {Object} FileReport
 | |
|  * @property {string} path
 | |
|  * @property {Record<number, { start: Location, end: Location }>} statementMap
 | |
|  * @property {{}} fnMap
 | |
|  * @property {{}} branchMap
 | |
|  * @property {Record<number, number>} s
 | |
|  * @property {{}} f
 | |
|  * @property {{}} b
 | |
|  */
 | |
| 
 | |
| /** @type {Record<string, FileReport>} */
 | |
| const coverageReport = Object.create(null);
 | |
| 
 | |
| for (const sourceFile of program.getSourceFiles()) {
 | |
| 	let file = sourceFile.fileName;
 | |
| 	if (isProjectFile(file)) {
 | |
| 		/** @type {FileReport} */
 | |
| 		const rep = {
 | |
| 			path: path.sep !== "/" ? file.replace(/\//g, path.sep) : file,
 | |
| 			statementMap: {},
 | |
| 			fnMap: {},
 | |
| 			branchMap: {},
 | |
| 			s: {},
 | |
| 			f: {},
 | |
| 			b: {}
 | |
| 		};
 | |
| 		coverageReport[rep.path] = rep;
 | |
| 		let statementIndex = 0;
 | |
| 
 | |
| 		/**
 | |
| 		 * @param {ts.Node} node the node to be walked
 | |
| 		 * @returns {void}
 | |
| 		 */
 | |
| 		const walkNode = node => {
 | |
| 			if (ts.isIdentifier(node) || node.kind === ts.SyntaxKind.ThisKeyword) {
 | |
| 				const type = typeChecker.getTypeAtLocation(node);
 | |
| 				if (type) {
 | |
| 					const { line, character } = ts.getLineAndCharacterOfPosition(
 | |
| 						sourceFile,
 | |
| 						node.getStart()
 | |
| 					);
 | |
| 					const {
 | |
| 						line: lineEnd,
 | |
| 						character: characterEnd
 | |
| 					} = ts.getLineAndCharacterOfPosition(sourceFile, node.getEnd());
 | |
| 					const typeText = typeChecker.typeToString(type);
 | |
| 					let isExternal = false;
 | |
| 
 | |
| 					/**
 | |
| 					 * @param {ts.Type} type the type to be checked
 | |
| 					 * @returns {void}
 | |
| 					 */
 | |
| 					const checkDecls = type => {
 | |
| 						if (!type.symbol) return;
 | |
| 						for (const decl of type.symbol.getDeclarations()) {
 | |
| 							const sourceFile = decl.getSourceFile();
 | |
| 							if (sourceFile && !isProjectFile(sourceFile.fileName))
 | |
| 								isExternal = true;
 | |
| 						}
 | |
| 					};
 | |
| 					if (node.parent && ts.isPropertyAccessExpression(node.parent)) {
 | |
| 						const expressionType = typeChecker.getTypeAtLocation(
 | |
| 							node.parent.expression
 | |
| 						);
 | |
| 						checkDecls(expressionType);
 | |
| 					}
 | |
| 					if (/^(<.*>)?\(/.test(typeText)) {
 | |
| 						checkDecls(type);
 | |
| 					}
 | |
| 					const isTyped =
 | |
| 						isExternal ||
 | |
| 						(!(type.flags & ts.TypeFlags.Any) && !/\bany\b/.test(typeText));
 | |
| 					rep.statementMap[statementIndex] = {
 | |
| 						start: {
 | |
| 							line: line + 1,
 | |
| 							column: character
 | |
| 						},
 | |
| 						end: {
 | |
| 							line: lineEnd + 1,
 | |
| 							column: characterEnd - 1
 | |
| 						}
 | |
| 					};
 | |
| 					rep.s[statementIndex] = isTyped ? typeText.length : 0;
 | |
| 					statementIndex++;
 | |
| 				}
 | |
| 			}
 | |
| 			node.forEachChild(walkNode);
 | |
| 		};
 | |
| 		sourceFile.forEachChild(walkNode);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| const outputDirectory = path.resolve(__dirname, "../coverage");
 | |
| mkdirp.sync(outputDirectory);
 | |
| fs.writeFileSync(
 | |
| 	path.resolve(outputDirectory, "coverage-types.json"),
 | |
| 	JSON.stringify(coverageReport),
 | |
| 	"utf-8"
 | |
| );
 |