UNPKG

typings-tester

Version:

TypeScript definition testing library

174 lines (137 loc) 4.34 kB
import * as ts from 'typescript'; import * as fs from 'fs'; import {dirname, normalize} from 'path'; function handleDiagnostics( type: string, diagnostics: Iterable<ts.Diagnostic>, bail: boolean = false, ) { const ret = []; for (const diagnostic of diagnostics) { const parts = [`${type}:`]; if (diagnostic.file && diagnostic.start != null) { const {line, character} = diagnostic.file.getLineAndCharacterOfPosition( diagnostic.start, ); parts.push( `${diagnostic.file.fileName}`, `(${line + 1}, ${character + 1})`, ); } const message = ts.flattenDiagnosticMessageText( diagnostic.messageText, '\n', ); parts.push(message); const text = parts.join(' '); if (bail) throw new Error(text); ret.push(text); } return ret; } function findLastNodeComment(node: ts.Node): string { const text = node.getFullText(); const commentRanges = ts.getLeadingCommentRanges(node.getFullText(), 0); if (commentRanges == null || commentRanges.length === 0) { return ''; } const {pos, end} = commentRanges[commentRanges.length - 1]; return text.substring(pos, end); } function forEachExpectedFailureNodes( node: ts.Node, cb: (node: ts.Node) => void, ) { if ( node.kind !== ts.SyntaxKind.SourceFile && findLastNodeComment(node).match(/^\/[\/*] typings:expect-error/) ) { cb(node); } else { ts.forEachChild(node, child => { forEachExpectedFailureNodes(child, cb); }); } } export function check( files: string[], tsConfigPath: string, bail: boolean = false, ) { const {config, error} = ts.readConfigFile(tsConfigPath, ts.sys.readFile); if (error) { throw new Error(ts.flattenDiagnosticMessageText(error.messageText, '\n')); } const {options, errors} = ts.convertCompilerOptionsFromJson( config.compilerOptions, dirname(tsConfigPath), ); if (errors.length > 0) { throw new Error( ts.flattenDiagnosticMessageText(errors[0].messageText, '\n'), ); } const allErrors: string[] = []; for (const file of files) { if (!fs.existsSync(file)) { throw new Error(`File '${file}' not found.`); } const program = ts.createProgram([file], options); const global = handleDiagnostics( 'Global', program.getGlobalDiagnostics(), bail, ); const syntax = handleDiagnostics( 'Syntactic', program.getSyntacticDiagnostics(), bail, ); allErrors.push(...global, ...syntax); for (const sourceFile of program.getSourceFiles()) { let semantic = program.getSemanticDiagnostics(sourceFile); const rootFileNames = program.getRootFileNames().map(normalize); const fileName = normalize(sourceFile.fileName); if (rootFileNames.indexOf(fileName) !== -1) { forEachExpectedFailureNodes(sourceFile, node => { const failures: ts.Diagnostic[] = []; const leftSemantics: ts.Diagnostic[] = []; for (const diag of semantic) { if ( diag.start != null && diag.length != null && node.pos <= diag.start && diag.start + diag.length <= node.end ) { failures.push(diag); } else { leftSemantics.push(diag); } } if (failures.length === 0) { const {line, character} = sourceFile.getLineAndCharacterOfPosition( node.getStart(), ); const message = `Expected error: ${sourceFile.fileName} ` + `(${line + 1}, ${character + 1}):\n` + node.getText(); if (bail) throw new Error(message); allErrors.push(message); } semantic = leftSemantics; }); } allErrors.push(...handleDiagnostics('Semantic', semantic, bail)); } } if (allErrors.length > 0) throw new Error(allErrors.join('\n\n')); } export function checkDirectory(path: string, bail: boolean = false) { const files = ts.sys.readDirectory(path, ['.ts', '.tsx']); const tsConfigPath = ts.findConfigFile(path, ts.sys.fileExists); if (!tsConfigPath) { throw new Error(`Cannot find TypeScript config file in ${path}.`); } check(files, tsConfigPath, bail); }