UNPKG

typescript-docs-verifier

Version:

Verifies that typescript examples in markdown files actually compile.

142 lines 6.68 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SnippetCompiler = exports.CompilationError = void 0; const fs_1 = __importDefault(require("fs")); const typescript_1 = __importDefault(require("typescript")); const CodeBlockExtractor_1 = require("./CodeBlockExtractor"); const LocalImportSubstituter_1 = require("./LocalImportSubstituter"); const CodeCompiler_1 = require("./CodeCompiler"); class CompilationError extends Error { constructor(diagnosticText, diagnostics) { var _a; super(diagnosticText); this.name = this.constructor.name; this.diagnosticText = diagnosticText; this.diagnosticCodes = (_a = diagnostics === null || diagnostics === void 0 ? void 0 : diagnostics.map(({ code }) => code)) !== null && _a !== void 0 ? _a : []; this.diagnostics = diagnostics !== null && diagnostics !== void 0 ? diagnostics : []; } } exports.CompilationError = CompilationError; class SnippetCompiler { constructor(workingDirectory, packageDefinition, project) { this.workingDirectory = workingDirectory; this.packageDefinition = packageDefinition; const configOptions = SnippetCompiler.loadTypeScriptConfig(packageDefinition.packageRoot, project); this.compilerOptions = configOptions.options; } static loadTypeScriptConfig(packageRoot, project) { const configFile = typescript_1.default.findConfigFile(packageRoot, typescript_1.default.sys.fileExists, project); if (!configFile) { throw new Error(`Unable to find TypeScript configuration file in ${packageRoot}`); } const fileContents = fs_1.default.readFileSync(configFile, "utf-8"); const { config: configJSON, error } = typescript_1.default.parseConfigFileTextToJson(configFile, fileContents); if (error) { throw new Error(`Error reading tsconfig from ${configFile}: ${typescript_1.default.flattenDiagnosticMessageText(error.messageText, typescript_1.default.sys.newLine)}`); } const parsedConfig = typescript_1.default.parseJsonConfigFileContent(configJSON, { fileExists: typescript_1.default.sys.fileExists, readDirectory: typescript_1.default.sys.readDirectory, readFile: typescript_1.default.sys.readFile, useCaseSensitiveFileNames: typescript_1.default.sys.useCaseSensitiveFileNames, }, packageRoot); return parsedConfig; } async compileSnippets(documentationFiles) { const results = []; const examples = await this.extractAllCodeBlocks(documentationFiles); for (const example of examples) { const result = await this.testCodeCompilation(example); results.push(result); // Yield to event loop await new Promise((resolve) => setImmediate(resolve)); } return results; } async extractAllCodeBlocks(documentationFiles) { const importSubstituter = new LocalImportSubstituter_1.LocalImportSubstituter(this.packageDefinition); const codeBlocks = await Promise.all(documentationFiles.map(async (file) => await this.extractFileCodeBlocks(file, importSubstituter))); return codeBlocks.flat(); } async extractFileCodeBlocks(file, importSubstituter) { const blocks = await CodeBlockExtractor_1.CodeBlockExtractor.extract(file); return blocks.map(({ code, type }, index) => { return { file, type, snippet: code, index: index + 1, sanitisedCode: this.sanitiseCodeBlock(importSubstituter, code), }; }); } sanitiseCodeBlock(importSubstituter, block) { const localisedBlock = importSubstituter.substituteLocalPackageImports(block); return localisedBlock; } async testCodeCompilation(example) { try { const { hasError, diagnostics } = await (0, CodeCompiler_1.compile)({ compilerOptions: this.compilerOptions, workingDirectory: this.workingDirectory, code: example.sanitisedCode, type: example.type, }); if (!hasError) { return { snippet: example.snippet, file: example.file, index: example.index, linesWithErrors: [], }; } const linesWithErrors = new Set(); const enrichedDiagnostics = diagnostics.map((diagnostic) => { if (typeof diagnostic.start !== "undefined") { const startLine = [...example.sanitisedCode.substring(0, diagnostic.start)].filter((char) => char === typescript_1.default.sys.newLine).length + 1; linesWithErrors.add(startLine); } return { ...diagnostic, file: diagnostic.file ? { ...diagnostic.file, fileName: `${example.file} → Code Block ${example.index}`, } : undefined, }; }); const formatter = process.stdout.isTTY ? typescript_1.default.formatDiagnosticsWithColorAndContext : typescript_1.default.formatDiagnostics; const diagnosticText = formatter(enrichedDiagnostics, { getCanonicalFileName: (fileName) => fileName, getCurrentDirectory: () => this.workingDirectory, getNewLine: () => typescript_1.default.sys.newLine, }); const error = new CompilationError(`⨯ Unable to compile TypeScript:\n${diagnosticText}`, enrichedDiagnostics); return { snippet: example.snippet, file: example.file, error: error, index: example.index, linesWithErrors: [...linesWithErrors], }; } catch (rawError) { const error = rawError instanceof Error ? rawError : new Error(String(rawError)); return { snippet: example.snippet, error: error, linesWithErrors: [], file: example.file, index: example.index, }; } } } exports.SnippetCompiler = SnippetCompiler; //# sourceMappingURL=SnippetCompiler.js.map