UNPKG

@graphql-eslint/eslint-plugin

Version:
184 lines (183 loc) 7.49 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.GraphQLRuleTester = void 0; /* eslint-env jest */ const fs_1 = require("fs"); const path_1 = require("path"); const eslint_1 = require("eslint"); const code_frame_1 = require("@babel/code-frame"); function indentCode(code, indent = 4) { return code.replace(/^/gm, ' '.repeat(indent)); } // A simple version of `SourceCodeFixer.applyFixes` // https://github.com/eslint/eslint/issues/14936#issuecomment-906746754 function applyFix(code, { range, text }) { return [code.slice(0, range[0]), text, code.slice(range[1])].join(''); } class GraphQLRuleTester extends eslint_1.RuleTester { constructor(parserOptions = {}) { const config = { parser: require.resolve('@graphql-eslint/eslint-plugin'), parserOptions: { ...parserOptions, skipGraphQLConfig: true, }, }; super(config); this.config = config; } fromMockFile(path) { return (0, fs_1.readFileSync)((0, path_1.resolve)(__dirname, `../tests/mocks/${path}`), 'utf-8'); } runGraphQLTests(ruleId, rule, tests) { const ruleTests = eslint_1.Linter.version.startsWith('8') ? tests : { valid: tests.valid.map(test => { if (typeof test === 'string') { return test; } // eslint-disable-next-line @typescript-eslint/no-unused-vars const { name, ...testCaseOptions } = test; return testCaseOptions; }), invalid: tests.invalid.map(test => { // ESLint 7 throws an error on CI - Unexpected top-level property "name" // eslint-disable-next-line @typescript-eslint/no-unused-vars const { name, ...testCaseOptions } = test; return testCaseOptions; }), }; super.run(ruleId, rule, ruleTests); const linter = new eslint_1.Linter(); linter.defineRule(ruleId, rule); const hasOnlyTest = [...tests.valid, ...tests.invalid].some(t => typeof t !== 'string' && t.only); // for (const [index, testCase] of tests.valid.entries()) { // const { name, code, filename, only }: RuleTester.ValidTestCase = // typeof testCase === 'string' ? { code: testCase } : testCase; // // if (hasOnlyTest && !only) { // continue; // } // // const verifyConfig = getVerifyConfig(ruleId, this.config, testCase); // defineParser(linter, verifyConfig.parser); // // const messages = linter.verify(code, verifyConfig, { filename }); // const codeFrame = printCode(code, { line: 0, column: 0 }); // // it(name || `Valid #${index + 1}\n${codeFrame}`, () => { // expect(messages).toEqual([]); // }); // } for (const [idx, testCase] of tests.invalid.entries()) { const { only, filename, options, name } = testCase; if (hasOnlyTest && !only) { continue; } const code = removeTrailingBlankLines(testCase.code); const verifyConfig = getVerifyConfig(ruleId, this.config, testCase); defineParser(linter, verifyConfig.parser); const messages = linter.verify(code, verifyConfig, filename); if (messages.length === 0) { throw new Error('Invalid case should have at least one error.'); } const codeFrame = indentCode(printCode(code, { line: 0, column: 0 })); const messageForSnapshot = ['#### ⌨️ Code', codeFrame]; if (options) { const opts = JSON.stringify(options, null, 2).slice(1, -1); messageForSnapshot.push('#### ⚙️ Options', indentCode(removeTrailingBlankLines(opts), 2)); } for (const [index, message] of messages.entries()) { if (message.fatal) { throw new Error(message.message); } const codeWithMessage = printCode(code, message, 1); messageForSnapshot.push(printWithIndex('#### ❌ Error', index, messages.length), indentCode(codeWithMessage)); const { suggestions } = message; // Don't print suggestions in snapshots for too big codes if (suggestions && (code.match(/\n/g) || '').length < 1000) { for (const [i, suggestion] of message.suggestions.entries()) { const title = printWithIndex('#### 💡 Suggestion', i, suggestions.length, suggestion.desc); const output = applyFix(code, suggestion.fix); const codeFrame = printCode(output, { line: 0, column: 0 }); messageForSnapshot.push(title, indentCode(codeFrame, 2)); } } } if (rule.meta.fixable) { const { fixed, output } = linter.verifyAndFix(code, verifyConfig, filename); if (fixed) { messageForSnapshot.push('#### 🔧 Autofix output', indentCode(printCode(output))); } } // @ts-expect-error -- we should import `vitest` but somebody could use globals from `jest` it(name || `Invalid #${idx + 1}`, () => { // @ts-expect-error -- ^ same expect(messageForSnapshot.join('\n\n')).toMatchSnapshot(); }); } } } exports.GraphQLRuleTester = GraphQLRuleTester; function removeTrailingBlankLines(text) { return text.replace(/^\s*\n/, '').trimEnd(); } function printWithIndex(title, index, total, description) { if (total > 1) { title += ` ${index + 1}/${total}`; } if (description) { title += `: ${description}`; } return title; } function getVerifyConfig(ruleId, testerConfig, testCase) { const { parser = testerConfig.parser, parserOptions, options } = testCase; return { ...testerConfig, parser, parserOptions: { ...testerConfig.parserOptions, ...parserOptions, }, rules: { [ruleId]: Array.isArray(options) ? ['error', ...options] : 'error', }, }; } const parsers = new WeakMap(); function defineParser(linter, parser) { if (!parser) { return; } if (!parsers.has(linter)) { parsers.set(linter, new Set()); } const defined = parsers.get(linter); if (!defined.has(parser)) { defined.add(parser); linter.defineParser(parser, require(parser)); } } function printCode(code, result = {}, linesOffset = Number.POSITIVE_INFINITY) { const { line, column, endLine, endColumn, message } = result; const location = {}; if (typeof line === 'number' && typeof column === 'number') { location.start = { line, column, }; } if (typeof endLine === 'number' && typeof endColumn === 'number') { location.end = { line: endLine, column: endColumn, }; } return (0, code_frame_1.codeFrameColumns)(code, location, { linesAbove: linesOffset, linesBelow: linesOffset, message, }); }