UNPKG

@graphql-codegen/testing

Version:
250 lines (249 loc) • 11.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.validateTs = validateTs; exports.compileTs = compileTs; const tslib_1 = require("tslib"); const path_1 = require("path"); const LZString = tslib_1.__importStar(require("lz-string")); // lz-string is a package which has CJS/ESM issues. So, we cannot do `import { something } from 'lz-string'` const typescript_1 = require("typescript"); const vitest_1 = require("vitest"); function validateTs(pluginOutput, options = { noEmitOnError: true, noImplicitAny: true, moduleResolution: typescript_1.ModuleResolutionKind.NodeJs, experimentalDecorators: true, emitDecoratorMetadata: true, target: typescript_1.ScriptTarget.ES5, typeRoots: resolveTypeRoots(), jsx: typescript_1.JsxEmit.React, allowJs: true, skipLibCheck: true, lib: [ (0, path_1.join)((0, path_1.dirname)(require.resolve('typescript')), 'lib.es5.d.ts'), (0, path_1.join)((0, path_1.dirname)(require.resolve('typescript')), 'lib.es6.d.ts'), (0, path_1.join)((0, path_1.dirname)(require.resolve('typescript')), 'lib.dom.d.ts'), (0, path_1.join)((0, path_1.dirname)(require.resolve('typescript')), 'lib.scripthost.d.ts'), (0, path_1.join)((0, path_1.dirname)(require.resolve('typescript')), 'lib.es2015.d.ts'), (0, path_1.join)((0, path_1.dirname)(require.resolve('typescript')), 'lib.esnext.d.ts'), ], module: typescript_1.ModuleKind.ESNext, }, isTsx = false, isStrict = false, suspenseErrors = [], compileProgram = false) { if (process.env.SKIP_VALIDATION) { return; } if (isStrict) { options.strictNullChecks = true; options.strict = true; options.strictBindCallApply = true; options.strictPropertyInitialization = true; options.alwaysStrict = true; options.strictFunctionTypes = true; } if (typescript_1.version.startsWith('6.')) { options.ignoreDeprecations ||= '6.0'; options.types ||= ['node']; } const contents = typeof pluginOutput === 'string' ? pluginOutput : [ ...new Set([ ...(pluginOutput.prepend || []), pluginOutput.content, ...(pluginOutput.append || []), ]), ].join('\n'); const cwd = resolveCallerDirectory(); const testFile = path_1.posix.join(cwd, `test-file.${isTsx ? 'tsx' : 'ts'}`); const errors = []; if (compileProgram) { const host = (0, typescript_1.createCompilerHost)(options); const program = (0, typescript_1.createProgram)([testFile], options, { ...host, getSourceFile: (fileName, languageVersion, onError, shouldCreateNewSourceFile) => { if (fileName === testFile) { return (0, typescript_1.createSourceFile)(fileName, contents, options.target); } return host.getSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile); }, writeFile() { }, useCaseSensitiveFileNames() { return false; }, getCanonicalFileName(filename) { return filename; }, getCurrentDirectory() { return cwd; }, getNewLine() { return '\n'; }, }); const emitResult = program.emit(); const allDiagnostics = emitResult.diagnostics; for (const diagnostic of allDiagnostics) { if (diagnostic.file) { const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start); const message = (0, typescript_1.flattenDiagnosticMessageText)(diagnostic.messageText, '\n'); errors.push(`${line + 1},${character + 1}: ${message} -> ${contents.split('\n')[line]}`); } else { errors.push(String((0, typescript_1.flattenDiagnosticMessageText)(diagnostic.messageText, '\n'))); } } } else { const result = (0, typescript_1.createSourceFile)(testFile, contents, typescript_1.ScriptTarget.ES2016, false, isTsx ? typescript_1.ScriptKind.TSX : undefined); const allDiagnostics = result.parseDiagnostics; if (allDiagnostics && allDiagnostics.length > 0) { for (const diagnostic of allDiagnostics) { if (diagnostic.file) { const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start); const message = (0, typescript_1.flattenDiagnosticMessageText)(diagnostic.messageText, '\n'); errors.push(`${line + 1},${character + 1}: ${message} -> ${contents.split('\n')[line]}`); } else { errors.push(String((0, typescript_1.flattenDiagnosticMessageText)(diagnostic.messageText, '\n'))); } } } } const relevantErrors = errors.filter(e => { if (e.includes('Cannot find module')) { return false; } for (const suspenseError of suspenseErrors) { if (e.includes(suspenseError)) { return false; } } return true; }); if (relevantErrors && relevantErrors.length > 0) { if (relevantErrors.length === 1) { throw new Error(relevantErrors[0]); } throw new AggregateError(relevantErrors, relevantErrors.join('\n')); } } function compileTs(contents, options = { noEmitOnError: true, noImplicitAny: true, moduleResolution: typescript_1.ModuleResolutionKind.NodeJs, allowSyntheticDefaultImports: true, experimentalDecorators: true, emitDecoratorMetadata: true, target: typescript_1.ScriptTarget.ES5, typeRoots: resolveTypeRoots(), jsx: typescript_1.JsxEmit.Preserve, allowJs: true, lib: [ (0, path_1.join)((0, path_1.dirname)(require.resolve('typescript')), 'lib.es5.d.ts'), (0, path_1.join)((0, path_1.dirname)(require.resolve('typescript')), 'lib.es6.d.ts'), (0, path_1.join)((0, path_1.dirname)(require.resolve('typescript')), 'lib.dom.d.ts'), (0, path_1.join)((0, path_1.dirname)(require.resolve('typescript')), 'lib.scripthost.d.ts'), (0, path_1.join)((0, path_1.dirname)(require.resolve('typescript')), 'lib.es2015.d.ts'), (0, path_1.join)((0, path_1.dirname)(require.resolve('typescript')), 'lib.esnext.asynciterable.d.ts'), ], module: typescript_1.ModuleKind.ESNext, }, isTsx = false, openPlayground = false) { if (process.env.SKIP_VALIDATION) { return; } try { const cwd = resolveCallerDirectory(); const testFile = path_1.posix.join(cwd, `test-file.${isTsx ? 'tsx' : 'ts'}`); const host = (0, typescript_1.createCompilerHost)(options); const program = (0, typescript_1.createProgram)([testFile], options, { ...host, getSourceFile: (fileName, languageVersion, onError, shouldCreateNewSourceFile) => { if (fileName === testFile) { return (0, typescript_1.createSourceFile)(fileName, contents, options.target); } return host.getSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile); }, writeFile() { }, useCaseSensitiveFileNames() { return false; }, getCanonicalFileName(filename) { return filename; }, getCurrentDirectory() { return cwd; }, getNewLine() { return '\n'; }, }); const emitResult = program.emit(); const allDiagnostics = emitResult.diagnostics; const errors = []; for (const diagnostic of allDiagnostics) { if (diagnostic.file) { const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start); const message = (0, typescript_1.flattenDiagnosticMessageText)(diagnostic.messageText, '\n'); errors.push(`${line + 1},${character + 1}: ${message} -> ${contents.split('\n')[line]}`); } else { errors.push(String((0, typescript_1.flattenDiagnosticMessageText)(diagnostic.messageText, '\n'))); } } const relevantErrors = errors.filter(e => !e.includes('Cannot find module')); if (relevantErrors && relevantErrors.length > 0) { throw new Error(relevantErrors.join('\n')); } } catch (e) { if (openPlayground) { const compressedCode = LZString.compressToEncodedURIComponent(contents); open('http://www.typescriptlang.org/play/#code/' + compressedCode); } throw e; } } /** * Resolve the directory of the test file currently being run. * * The file these helpers type-check exists only in memory, but TypeScript still needs a real * directory to anchor Node module resolution to. `process.cwd()` is not usable: it is the repo * root, and under pnpm's isolated layout a package's dependencies live in that package's own * `node_modules`. * * The result uses forward slashes, as does the synthetic file path built from it: TypeScript * normalizes every path it hands back to the host that way, so on Windows a backslash path would * never match the `fileName === testFile` check in `getSourceFile`. * * Throws outside a vitest test rather than falling back to `process.cwd()`, which would silently * reintroduce unresolved imports. */ const resolveCallerDirectory = () => { const testPath = vitest_1.expect.getState().testPath; if (!testPath) { throw new Error('validateTs / compileTs must be called from within a vitest test'); } return (0, path_1.dirname)(testPath).replace(/\\/g, '/'); }; /** * Resolve the `@types` directory that actually contains `@types/node`. * * This used to be derived from `require.resolve('typescript')` as * `<ts>/lib/../../../@types`, which only lands on `node_modules/@types` in a flat * (npm/yarn) layout. Under pnpm's default isolated layout `require.resolve` returns the * realpath inside the virtual store, so it pointed at * `node_modules/.pnpm/typescript@<version>/node_modules/@types` -- a directory that does * not exist -- and no ambient typings were ever loaded. * * Locating the directory from `@types/node` itself keeps it correct under every layout. */ const resolveTypeRoots = () => { // A `typeRoots` entry is a *container* directory, not a type package: TypeScript resolves // every name in `types` as `<typeRoot>/<name>`, so `types: ['node']` looks for // `<typeRoot>/node`. Hence two steps up from the manifest -- to the package, then to the // `@types` directory holding it. const nodeTypesPackageDir = (0, path_1.dirname)(require.resolve('@types/node/package.json')); // <..>/@types/node return [(0, path_1.dirname)(nodeTypesPackageDir)]; // <..>/@types };