Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { PslDiagnostic } from '@internal/framework-components/psl-ast';
import { notOk, ok, type Result } from '@internal/utils/result';
import type { ModelSymbol } from '../../symbol-table';
import type { ExpressionAst } from '../../syntax/ast/expressions';
import { FunctionCallAst } from '../../syntax/ast/expressions';
import { IdentifierAst } from '../../syntax/ast/identifier';
import type {
AttributeCtx,
Expand All @@ -12,14 +13,32 @@ import type {
} from '../types';
import { leafDiagnostic } from './diagnostic';

/**
* A function call where a field name belongs is almost always a Prisma-style
* sort annotation (`field(sort: Desc)`). Name the real problem — arguments
* are not accepted in field lists — instead of the cryptic "Expected a field
* name".
*/
function fieldNameProblem(arg: ExpressionAst): string {
const call = FunctionCallAst.cast(arg.syntax);
const callee = call?.path() ?? [];
if (callee.length === 1 && callee[0] !== undefined) {
return (
`Expected a bare field name, but found "${callee[0]}(...)" — ` +
'field lists do not accept arguments, so (sort: Asc/Desc) is not supported here.'
);
}
return 'Expected a field name';
}

function parseFieldName(
arg: ExpressionAst,
ctx: AttributeCtx,
model: ModelSymbol | undefined,
): Result<string, readonly PslDiagnostic[]> {
const identifier = IdentifierAst.cast(arg.syntax);
if (identifier === undefined) {
return notOk([leafDiagnostic(ctx, arg, 'Expected a field name')]);
return notOk([leafDiagnostic(ctx, arg, fieldNameProblem(arg))]);
}
const name = identifier.name();
if (name === undefined) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,20 @@ describe('fieldRef', () => {
expect(result.ok).toBe(false);
if (!result.ok) expect(result.failure[0]?.code).toBe('PSL_INVALID_ATTRIBUTE_SYNTAX');
});

it('names the sort-argument problem instead of "Expected a field name"', () => {
const { expr, ctx } = argOf('id(sort: Desc)');

const result = fieldRef().parse(expr, ctx);

expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.failure).toHaveLength(1);
expect(result.failure[0]?.code).toBe('PSL_INVALID_ATTRIBUTE_SYNTAX');
expect(result.failure[0]?.message).toMatch(/sort/i);
expect(result.failure[0]?.message).not.toBe('Expected a field name');
}
});
});

describe('entityRef', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -813,6 +813,32 @@ model OrderItem {
indexes: [{ columns: ['body'] }],
});
});

it('explains sort annotations in @@index field lists instead of "Expected a field name"', () => {
const document = symbolTableInputFromParseArgs({
schema: `model Item {
id Int @id
createdAt DateTime
@@index([createdAt(sort: Desc)])
}`,
sourceId: 'schema.prisma',
});

const result = interpretPslDocumentToSqlContract({
...document,
controlMutationDefaults: builtinControlMutationDefaults,
});
expect(result.ok).toBe(false);
if (result.ok) return;
expect(
result.failure.diagnostics.some(
(d) =>
d.code === 'PSL_INVALID_ATTRIBUTE_SYNTAX' &&
/sort/i.test(d.message) &&
d.message !== 'Expected a field name',
),
).toBe(true);
});
});

describe('per-target namespace resolution', () => {
Expand Down