input-number-guard.test.ts 2.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
  1. import { execFileSync } from 'node:child_process';
  2. import { readFileSync } from 'node:fs';
  3. import { describe, expect, it } from 'vitest';
  4. // The #6121/#6127 guard is hand-written JS walking oxlint's AST, so it can go
  5. // silently dead on an oxlint bump. These tests fail loudly when it does.
  6. const FIXTURES = 'tools/oxlint/__fixtures__';
  7. const RULE = 'input-number(no-synthetic-clear)';
  8. function runGuard(target: string): string {
  9. try {
  10. // .bin/oxlint is a sh shim Windows can't spawn; run the node entry directly.
  11. execFileSync(
  12. process.execPath,
  13. ['node_modules/oxlint/bin/oxlint', '-c', `${FIXTURES}/guard.oxlintrc.json`, target],
  14. { encoding: 'utf8', stdio: 'pipe' },
  15. );
  16. return '';
  17. } catch (error) {
  18. const { status, stdout } = error as { status?: number | null; stdout?: string };
  19. if (typeof status !== 'number') throw error;
  20. return String(stdout ?? '');
  21. }
  22. }
  23. describe('input-number-guard oxlint plugin', () => {
  24. it('rejects every shape that turns a cleared InputNumber into a stored number', () => {
  25. const output = runGuard(`${FIXTURES}/synthetic-clear.tsx`);
  26. const hits = output.split('\n').filter((line) => line.includes(RULE));
  27. // Number(v) || N, the typeof ternary, and v ?? N.
  28. expect(hits).toHaveLength(3);
  29. expect(output).toContain('#6127');
  30. });
  31. it('accepts a handler wrapped with onNumber()', () => {
  32. expect(runGuard(`${FIXTURES}/ok.tsx`)).not.toContain(RULE);
  33. });
  34. });
  35. describe('input-number-guard wiring', () => {
  36. const config = JSON.parse(readFileSync('.oxlintrc.json', 'utf8')) as {
  37. jsPlugins: string[];
  38. rules: Record<string, unknown>;
  39. overrides: { files: string[]; rules: Record<string, unknown> }[];
  40. };
  41. it('loads the plugin and scopes the rule to the pages that regressed', () => {
  42. expect(config.jsPlugins).toContain('./tools/oxlint/input-number-guard.mjs');
  43. expect(config.rules['input-number/no-synthetic-clear']).toBe('off');
  44. const enabled = config.overrides.find(
  45. (o) => o.rules['input-number/no-synthetic-clear'] === 'error',
  46. );
  47. expect(enabled?.files).toEqual(['src/pages/settings/**/*.tsx', 'src/pages/xray/**/*.tsx']);
  48. const exempt = config.overrides.find(
  49. (o) => o.rules['input-number/no-synthetic-clear'] === 'off',
  50. );
  51. expect(exempt?.files).toEqual(['src/pages/xray/**/*Modal.tsx']);
  52. });
  53. });