Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import jsPlugin from '@eslint/js';
import stylistic from '@stylistic/eslint-plugin';
import eslintPluginJsonc from 'eslint-plugin-jsonc';
import perfectionist from 'eslint-plugin-perfectionist';
import zodPlugin from 'eslint-plugin-zod';
import { defineConfig } from 'eslint/config';
import globals from 'globals';
import tseslint from 'typescript-eslint';
Expand Down Expand Up @@ -36,6 +37,7 @@ export default defineConfig(
),
},
},
zod: zodPlugin,
},
rules: {
'custom/require-function-tag-in-arrow-functions': [
Expand Down Expand Up @@ -64,6 +66,26 @@ export default defineConfig(
],
},
],
// Zod plugin rules
'zod/array-style': ['error', { style: 'function' }],
'zod/consistent-import-source': ['error', { sources: ['zod'] }],
'zod/consistent-object-schema-type': [
'error',
{ allow: ['object', 'looseObject', 'strictObject'] },
],
'zod/no-any-schema': 'off', // intentional for assertion library
'zod/no-empty-custom-schema': 'error',
'zod/no-number-schema-with-int': 'error',
'zod/no-optional-and-default-together': 'error',
'zod/no-throw-in-refine': 'error',
'zod/no-unknown-schema': 'off', // intentional for assertion library
'zod/prefer-enum-over-literal-union': 'error',
'zod/prefer-meta': 'error',
'zod/prefer-meta-last': 'error',
'zod/require-brand-type-parameter': 'error',
'zod/require-error-message': 'off', // too noisy
'zod/require-schema-suffix': 'off', // not our convention
'zod/schema-error-property-style': 'error',
},
},
{
Expand Down
24 changes: 24 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"eslint": "9.39.2",
"eslint-plugin-jsonc": "2.21.0",
"eslint-plugin-perfectionist": "5.1.0",
"eslint-plugin-zod": "3.0.1",
"fast-check": "4.5.2",
"globals": "16.5.0",
"husky": "9.1.7",
Expand Down
2 changes: 1 addition & 1 deletion packages/bupkis/src/assertion/impl/async-iterable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* @showGroup
*/
import { inspect } from 'node:util';
import { z } from 'zod/v4';
import { z } from 'zod';

import {
AsyncIterableOrIteratorSchema,
Expand Down
9 changes: 5 additions & 4 deletions packages/bupkis/src/assertion/impl/sync-basic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -531,10 +531,11 @@ export const objectAssertion = createAssertion(
.unknown()
.nonoptional()
.refine((value) => typeof value == 'object' && value !== null)
.describe(
'Returns true for any non-null value where `typeof value` is `object`',
)
.register(BupkisRegistry, { name: 'Object' }),
.register(BupkisRegistry, { name: 'Object' })
.meta({
description:
'Returns true for any non-null value where `typeof value` is `object`',
}),
);

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/bupkis/src/assertion/impl/sync-iterable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* @showGroup
*/
import { inspect } from 'node:util';
import { z } from 'zod/v4';
import { z } from 'zod';

import {
NonNegativeIntegerSchema,
Expand Down
6 changes: 3 additions & 3 deletions packages/bupkis/src/assertion/slotify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export const slotify = <const Parts extends AssertionParts>(
const slots = parts.flatMap((part, index) => {
const result: z.ZodType[] = [];
if (index === 0 && isPhrase(part)) {
result.push(z.unknown().describe('subject'));
result.push(z.unknown().meta({ description: 'subject' }));
}

if (isPhraseLiteralChoice(part)) {
Expand Down Expand Up @@ -171,7 +171,7 @@ const createPhraseLiteralChoiceSchema = (

const schema = z
.literal(part)
.brand('string-literal')
.brand<'string-literal'>('string-literal')
.register(BupkisRegistry, {
[kStringLiteral]: true,
values: part,
Expand Down Expand Up @@ -202,7 +202,7 @@ const createPhraseLiteralSchema = (

const schema = z
.literal(part)
.brand('string-literal')
.brand<'string-literal'>('string-literal')
.register(BupkisRegistry, {
[kStringLiteral]: true,
value: part,
Expand Down
64 changes: 41 additions & 23 deletions packages/bupkis/src/internal-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,35 +25,48 @@ const AssertionFailureSchema = z
actual: z
.unknown()
.optional()
.describe('The actual value or description of what actually occurred'),
.meta({
description:
'The actual value or description of what actually occurred',
}),
diff: z
.string()
.optional()
.describe('Pre-computed diff string that bypasses jest-diff'),
.meta({
description: 'Pre-computed diff string that bypasses jest-diff',
}),
diffOptions: z
.record(z.string(), z.unknown())
.optional()
.describe('Override options for jest-diff'),
.meta({ description: 'Override options for jest-diff' }),
expected: z
.unknown()
.optional()
.describe(
'The expected value or description of what was expected to occur',
),
.meta({
description:
'The expected value or description of what was expected to occur',
}),
formatActual: z
.function()
.optional()
.describe('Custom formatter for actual value in diff output'),
.meta({
description: 'Custom formatter for actual value in diff output',
}),
formatExpected: z
.function()
.optional()
.describe('Custom formatter for expected value in diff output'),
.meta({
description: 'Custom formatter for expected value in diff output',
}),
message: z
.string()
.optional()
.describe('A human-readable message describing the failure'),
.meta({ description: 'A human-readable message describing the failure' }),
})
.describe('Potential return type of an assertion implementation function');
.meta({
description:
'Potential return type of an assertion implementation function',
});

/**
* @internal
Expand All @@ -62,7 +75,7 @@ const ZodTypeSchema = z
.custom<z.ZodType>(isZodType, {
error: 'Must be a Zod schema',
})
.describe('A Zod schema within AssertionParts');
.meta({ description: 'A Zod schema within AssertionParts' });

/**
* @internal
Expand All @@ -71,7 +84,7 @@ const StandardSchemaSchema = z
.custom(isStandardSchema, {
error: 'Must be a Standard Schema v1',
})
.describe('A Standard Schema v1 within AssertionParts');
.meta({ description: 'A Standard Schema v1 within AssertionParts' });

/**
* Schema that accepts either Zod or Standard Schema validators.
Expand All @@ -80,11 +93,13 @@ const StandardSchemaSchema = z
*/
const SchemaSchema = z
.union([ZodTypeSchema, StandardSchemaSchema])
.describe('A Zod schema or Standard Schema v1');
.meta({ description: 'A Zod schema or Standard Schema v1' });

/** @internal */
const BaseAssertionParseRequestSchema = z.object({
subject: z.unknown().describe('The subject value to be validated'),
subject: z
.unknown()
.meta({ description: 'The subject value to be validated' }),
});

/**
Expand All @@ -109,17 +124,18 @@ const PhraseLiteralSchema = z
error: 'Phrase literals may not begin with "not "',
})
.min(1, { error: 'Phrase literals must be at least 1 character long' })
.describe('A phrase literal within AssertionParts');
.meta({ description: 'A phrase literal within AssertionParts' });

/**
* @internal
*/
const PhraseLiteralChoiceSchema = z
.array(PhraseLiteralSchema)
.min(1, { error: 'Phrase literal choices must have at least one option' })
.describe(
'A choice of phrase literals, represented as an array of strings, within AssertionParts',
);
.meta({
description:
'A choice of phrase literals, represented as an array of strings, within AssertionParts',
});
/**
* @internal
*/
Expand All @@ -137,7 +153,7 @@ const AssertionImplSchemaSync = z
]),
}),
])
.describe('A synchronous assertion implementation function');
.meta({ description: 'A synchronous assertion implementation function' });

const AssertionImplSchemaAsync = z
.union([
Expand All @@ -158,7 +174,7 @@ const AssertionImplSchemaAsync = z
]),
}),
])
.describe('An async assertion implementation function');
.meta({ description: 'An async assertion implementation function' });

/**
* Checks if a value is a schema (Zod or StandardSchema)
Expand Down Expand Up @@ -218,7 +234,9 @@ const AssertionPartsSchema = z
'Assertions must have a phrase at position 0 (phrase-first shorthand) or position 1 (after subject schema)',
},
)
.describe('Assertion "parts" which define the input of an assertion');
.meta({
description: 'Assertion "parts" which define the input of an assertion',
});

/**
* Type guard for a {@link AssertionFailure}.
Expand Down Expand Up @@ -257,7 +275,7 @@ export const isAssertionParseRequest = (
*/
export const CreateAssertionInputSchema = z
.tuple([AssertionPartsSchema, AssertionImplSchemaSync])
.describe('Parameters for createAssertion()');
.meta({ description: 'Parameters for createAssertion()' });

/**
* Schema for the input parameters of {@link createAsyncAssertion}.
Expand All @@ -266,4 +284,4 @@ export const CreateAssertionInputSchema = z
*/
export const CreateAssertionInputSchemaAsync = z
.tuple([AssertionPartsSchema, AssertionImplSchemaAsync])
.describe('Parameters for createAsyncAssertion()');
.meta({ description: 'Parameters for createAsyncAssertion()' });
9 changes: 6 additions & 3 deletions packages/bupkis/src/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,18 @@ export const BupkisRegistry = z.registry<BupkisMeta>();
* Base schema for all metadata
*/
const BaseBupkisMetadataSchema = z.object({
description: z.string().optional().describe('Human-friendly description'),
description: z
.string()
.optional()
.meta({ description: 'Human-friendly description' }),
name: z
.string()
.optional()
.describe('Name used when rendering Assertion as a string'),
.meta({ description: 'Name used when rendering Assertion as a string' }),
parameter: z
.string()
.optional()
.describe('Parameter "type" to use in documentation'),
.meta({ description: 'Parameter "type" to use in documentation' }),
});

/**
Expand Down
Loading
Loading