diff --git a/jest.config.js b/jest.config.js index 37c2359..d2f390a 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,4 +1,5 @@ module.exports = { + globalSetup: '/jest.globalSetup.js', transform: { '^.+\\.ts$': 'ts-jest', }, diff --git a/jest.globalSetup.js b/jest.globalSetup.js new file mode 100644 index 0000000..09070df --- /dev/null +++ b/jest.globalSetup.js @@ -0,0 +1,11 @@ +const {execFileSync} = require('node:child_process'); +const path = require('node:path'); + +module.exports = function globalSetup() { + // Build the bundle once for the whole test run so tests that exercise the + // published artifact can require it without each rebuilding it themselves. + execFileSync('npm', ['run', 'build'], { + cwd: path.resolve(__dirname), + stdio: 'pipe', + }); +}; diff --git a/src/bundle.test.ts b/src/bundle.test.ts index fbc6b0c..c7f4057 100644 --- a/src/bundle.test.ts +++ b/src/bundle.test.ts @@ -5,18 +5,9 @@ import {execFileSync} from 'node:child_process'; const requireFromTest = createRequire(__filename); -function buildBundle(): string { - execFileSync('npm', ['run', 'build'], { - cwd: path.resolve(__dirname, '..'), - stdio: 'pipe', - }); - - return path.resolve(__dirname, '../dist/index.cjs'); -} - describe('published bundle', () => { it('inlines metadata for bundled plugins that read their own package.json', () => { - const bundlePath = buildBundle(); + const bundlePath = path.resolve(__dirname, '../dist/index.cjs'); const bundle = fs.readFileSync(bundlePath, 'utf8'); expect(bundle).not.toContain('cjsRequire("../package.json")'); diff --git a/src/configs/typescript.test.ts b/src/configs/typescript.test.ts new file mode 100644 index 0000000..b08616f --- /dev/null +++ b/src/configs/typescript.test.ts @@ -0,0 +1,36 @@ +import path from 'node:path'; +import {execFileSync} from 'node:child_process'; + +const bundlePath = path.resolve(__dirname, '../../dist/index.cjs'); + +function lint(filename: string, code: string): Array<{ruleId: string | null, message: string}> { + const script = ` + const {Linter} = require('eslint'); + const {configs} = require(${JSON.stringify(bundlePath)}); + const messages = new Linter().verify( + ${JSON.stringify(code)}, + configs.typescript, + {filename: ${JSON.stringify(filename)}}, + ); + process.stdout.write(JSON.stringify(messages)); + `; + + const output = execFileSync('node', ['-e', script], { + cwd: path.resolve(__dirname, '../..'), + encoding: 'utf8', + }); + + return JSON.parse(output) as Array<{ruleId: string | null, message: string}>; +} + +describe('typescript', () => { + it('should not apply type-checked rules to pure JS files', () => { + const messages = lint('file.js', 'const x = 1;\n'); + + const typeAwareErrors = messages.filter( + message => message.message.includes('requires type information'), + ); + + expect(typeAwareErrors).toEqual([]); + }); +}); diff --git a/src/configs/typescript.ts b/src/configs/typescript.ts index 524903b..16b1773 100644 --- a/src/configs/typescript.ts +++ b/src/configs/typescript.ts @@ -90,15 +90,24 @@ const baseRules: Linter.RulesRecord = { 'no-void': 'off', }; +const typescriptFiles = ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts']; + // Factory function to create TypeScript config with the plugin reference export function createTypescriptConfig(plugin: ESLint.Plugin, javascriptConfig: Linter.Config[]): Linter.Config[] { return [ ...javascriptConfig, - ...tseslint.configs.recommendedTypeChecked, + ...tseslint.configs + .recommendedTypeChecked + .map( + config => ({ + ...config, + files: typescriptFiles, + }), + ), jest.configs['flat/recommended'], { name: '@croct/typescript', - files: ['**/*.ts', '**/*.tsx'], + files: typescriptFiles, plugins: { '@stylistic': stylistic, '@croct': plugin,