UNPKG

@typespec/compiler

Version:

TypeSpec compiler and standard library

124 lines 6 kB
import { ok, strictEqual } from "assert"; import { applyCodeFix as applyCodeFixReal } from "../core/code-fixes.js"; import { createDiagnosticCollector } from "../core/diagnostics.js"; import { createLinterRuleContext } from "../core/linter.js"; import { navigateProgram } from "../core/semantic-walker.js"; import { expectDiagnosticEmpty, expectDiagnostics } from "./expect.js"; import { resolveVirtualPath, trimBlankLines } from "./test-utils.js"; export function createLinterRuleTester( // eslint-disable-next-line @typescript-eslint/no-deprecated runner, ruleDef, libraryName) { return { expect, }; function expect(code) { return { toBeValid, toEmitDiagnostics, applyCodeFix, }; async function toBeValid() { const [_, diagnostics] = await compileAndDiagnose(code); expectDiagnosticEmpty(diagnostics); } async function toEmitDiagnostics(match) { const [result, diagnostics] = await compileAndDiagnose(code); let expected; if (typeof match === "function") { if ("autoCodeOffset" in runner) { throw new Error(".toEmitDiagnostics with a function match can only be used with a TesterInstance"); } expected = match(result); } else { expected = match; } expectDiagnostics(diagnostics, expected); } function applyCodeFix(fixId) { return { toEqual }; async function toEqual(expectedCode) { const [_, diagnostics] = await compileAndDiagnose(code); const codefix = diagnostics[0].codefixes?.find((x) => x.id === fixId); ok(codefix, `Codefix with id "${fixId}" not found.`); if (typeof expectedCode === "string") { await assertSingleFileCodeFix(codefix, expectedCode); } else { await assertMultiFileCodeFix(codefix, expectedCode); } } async function assertSingleFileCodeFix(codefix, expectedCode) { let content; const host = { ...runner.program.host, writeFile: (name, newContent) => { content = newContent; return Promise.resolve(); }, }; await applyCodeFixReal(host, codefix); ok(content, "No content was written to the host."); const fs = "keys" in runner.fs ? runner.fs : runner.fs.fs; const offset = fs.get(resolveVirtualPath("./main.tsp"))?.indexOf(code); strictEqual(trimBlankLines(content.slice(offset)), trimBlankLines(expectedCode)); } async function assertMultiFileCodeFix(codefix, expectedFiles) { const writtenFiles = new Map(); const host = { ...runner.program.host, writeFile: (name, newContent) => { writtenFiles.set(name, newContent); return Promise.resolve(); }, }; await applyCodeFixReal(host, codefix); ok(writtenFiles.size > 0, "No content was written to the host."); const fs = "keys" in runner.fs ? runner.fs : runner.fs.fs; for (const [filename, expected] of Object.entries(expectedFiles)) { const virtualPath = resolveVirtualPath(filename); const written = writtenFiles.get(virtualPath); ok(written !== undefined, `Expected file "${filename}" to be written by the code fix but it was not. Written files: ${[...writtenFiles.keys()].join(", ")}`); const inputCode = typeof code === "string" ? code : code[filename]; const originalContent = fs.get(virtualPath); const offset = typeof inputCode === "string" && originalContent ? originalContent.indexOf(inputCode) : undefined; const actual = offset !== undefined && offset >= 0 ? written.slice(offset) : written; strictEqual(trimBlankLines(actual), trimBlankLines(expected)); } } } } async function compileAndDiagnose(code) { const compilerOptions = { parseOptions: { comments: true } }; let res; let codeDiagnostics; if (isLegacyTestRunner(runner)) { if (typeof code !== "string") { throw new Error("Only string code is supported with BasicTestRunner. Use Tester.createInstance()"); } codeDiagnostics = await runner.diagnose(code, compilerOptions); } else { [res, codeDiagnostics] = await runner.compileAndDiagnose(code, { compilerOptions }); } expectDiagnosticEmpty(codeDiagnostics); const diagnostics = createDiagnosticCollector(); const rule = { ...ruleDef, id: `${libraryName}/${ruleDef.name}` }; const context = createLinterRuleContext(runner.program, rule, rule.defaultOptions ?? {}, diagnostics); const listener = ruleDef.create(context); navigateProgram(runner.program, listener); if (listener.exit) { await listener.exit(runner.program); } // No diagnostics should have been reported to the program. If it happened the rule is calling reportDiagnostic directly and should NOT be doing that. expectDiagnosticEmpty(runner.program.diagnostics); return [res, diagnostics.diagnostics]; } } // eslint-disable-next-line @typescript-eslint/no-deprecated function isLegacyTestRunner(tester) { return "autoCodeOffset" in tester; } //# sourceMappingURL=rule-tester.js.map