@prisma-next/psl-parser 0.14.0-dev.7 → 0.14.0-dev.71
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{declarations-D9h_ihD3.mjs → declarations-DR6To8_k.mjs} +295 -55
- package/dist/declarations-DR6To8_k.mjs.map +1 -0
- package/dist/format.d.mts +1 -1
- package/dist/format.mjs +2 -2
- package/dist/index.d.mts +95 -112
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +374 -60
- package/dist/index.mjs.map +1 -1
- package/dist/interpret.d.mts +29 -0
- package/dist/interpret.d.mts.map +1 -0
- package/dist/interpret.mjs +9 -0
- package/dist/interpret.mjs.map +1 -0
- package/dist/{parse-BjZ1LPe6.d.mts → parse-BazJr7Ye.d.mts} +128 -51
- package/dist/parse-BazJr7Ye.d.mts.map +1 -0
- package/dist/{parse-DhEV6av6.mjs → parse-CdeXr0T3.mjs} +28 -7
- package/dist/parse-CdeXr0T3.mjs.map +1 -0
- package/dist/symbol-table-C-AH04Ug.d.mts +127 -0
- package/dist/symbol-table-C-AH04Ug.d.mts.map +1 -0
- package/dist/syntax.d.mts +20 -2
- package/dist/syntax.d.mts.map +1 -1
- package/dist/syntax.mjs +43 -3
- package/dist/syntax.mjs.map +1 -0
- package/package.json +9 -7
- package/src/attribute-spec/combinators/bool.ts +19 -0
- package/src/attribute-spec/combinators/diagnostic.ts +15 -0
- package/src/attribute-spec/combinators/entity-ref.ts +24 -0
- package/src/attribute-spec/combinators/field-ref.ts +36 -0
- package/src/attribute-spec/combinators/identifier.ts +16 -0
- package/src/attribute-spec/combinators/int.ts +19 -0
- package/src/attribute-spec/combinators/list.ts +43 -0
- package/src/attribute-spec/combinators/one-of.ts +29 -0
- package/src/attribute-spec/combinators/record.ts +43 -0
- package/src/attribute-spec/combinators/str.ts +19 -0
- package/src/attribute-spec/field-attribute.ts +27 -0
- package/src/attribute-spec/interpret.ts +154 -0
- package/src/attribute-spec/model-attribute.ts +27 -0
- package/src/attribute-spec/optional.ts +8 -0
- package/src/attribute-spec/types.ts +72 -0
- package/src/block-reconstruction.ts +1 -0
- package/src/exports/index.ts +30 -0
- package/src/exports/interpret.ts +2 -0
- package/src/exports/syntax.ts +25 -5
- package/src/interpret.ts +40 -0
- package/src/parse.ts +23 -5
- package/src/resolve.ts +4 -1
- package/src/source-file.ts +25 -0
- package/src/syntax/ast/attributes.ts +5 -6
- package/src/syntax/ast/declarations.ts +51 -26
- package/src/syntax/ast/expressions.ts +12 -13
- package/src/syntax/ast/identifier.ts +2 -3
- package/src/syntax/ast/qualified-name.ts +28 -19
- package/src/syntax/ast/type-annotation.ts +4 -5
- package/src/syntax/ast-helpers.ts +27 -3
- package/src/syntax/navigation.ts +55 -0
- package/src/syntax/red.ts +317 -42
- package/dist/declarations-D9h_ihD3.mjs.map +0 -1
- package/dist/parse-BjZ1LPe6.d.mts.map +0 -1
- package/dist/parse-DhEV6av6.mjs.map +0 -1
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import type { PslDiagnostic, PslSpan } from '@prisma-next/framework-components/psl-ast';
|
|
2
|
+
import { blindCast } from '@prisma-next/utils/casts';
|
|
3
|
+
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
4
|
+
import { nodePslSpan } from '../resolve';
|
|
5
|
+
import type { FieldAttributeAst, ModelAttributeAst } from '../syntax/ast/attributes';
|
|
6
|
+
import type { AttributeArgAst } from '../syntax/ast/expressions';
|
|
7
|
+
import { ATTRIBUTE_DIAGNOSTIC_CODE } from './combinators/diagnostic';
|
|
8
|
+
import type { ArgType, AttributeSpec, InterpretCtx, OptionalArgType, Param } from './types';
|
|
9
|
+
|
|
10
|
+
export function interpretAttribute<Out>(
|
|
11
|
+
attrNode: FieldAttributeAst | ModelAttributeAst,
|
|
12
|
+
spec: AttributeSpec<Out>,
|
|
13
|
+
ctx: InterpretCtx,
|
|
14
|
+
): Result<Out, readonly PslDiagnostic[]> {
|
|
15
|
+
const diagnostics: PslDiagnostic[] = [];
|
|
16
|
+
const attributeSpan = nodePslSpan(attrNode.syntax, ctx.sourceFile);
|
|
17
|
+
|
|
18
|
+
const output: Record<string, unknown> = {};
|
|
19
|
+
const seen = new Set<string>();
|
|
20
|
+
let positionalSlot = 0;
|
|
21
|
+
let reportedExcess = false;
|
|
22
|
+
|
|
23
|
+
for (const arg of attrNode.argList()?.args() ?? []) {
|
|
24
|
+
const name = arg.name()?.name();
|
|
25
|
+
|
|
26
|
+
let key: string;
|
|
27
|
+
let param: Param<unknown>;
|
|
28
|
+
if (name === undefined) {
|
|
29
|
+
const posParam = spec.positional[positionalSlot];
|
|
30
|
+
if (posParam === undefined) {
|
|
31
|
+
if (!reportedExcess) {
|
|
32
|
+
diagnostics.push(
|
|
33
|
+
diagnostic(
|
|
34
|
+
`Attribute "${spec.name}" received too many positional arguments`,
|
|
35
|
+
ctx,
|
|
36
|
+
attributeSpan,
|
|
37
|
+
),
|
|
38
|
+
);
|
|
39
|
+
reportedExcess = true;
|
|
40
|
+
}
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
positionalSlot += 1;
|
|
44
|
+
key = posParam.key;
|
|
45
|
+
param = posParam.type;
|
|
46
|
+
} else {
|
|
47
|
+
const namedParam = Object.hasOwn(spec.named, name) ? spec.named[name] : undefined;
|
|
48
|
+
if (namedParam === undefined) {
|
|
49
|
+
diagnostics.push(
|
|
50
|
+
diagnostic(
|
|
51
|
+
`Attribute "${spec.name}" received unknown argument "${name}"`,
|
|
52
|
+
ctx,
|
|
53
|
+
nodePslSpan(arg.syntax, ctx.sourceFile),
|
|
54
|
+
),
|
|
55
|
+
);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
key = name;
|
|
59
|
+
param = namedParam;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (seen.has(key)) {
|
|
63
|
+
diagnostics.push(
|
|
64
|
+
diagnostic(
|
|
65
|
+
`Attribute "${spec.name}" received duplicate argument "${key}"`,
|
|
66
|
+
ctx,
|
|
67
|
+
nodePslSpan(arg.syntax, ctx.sourceFile),
|
|
68
|
+
),
|
|
69
|
+
);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
seen.add(key);
|
|
73
|
+
const result = parseArgValue(arg, param, ctx, diagnostics);
|
|
74
|
+
if (result.ok) output[key] = result.value;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const finalized = new Set<string>();
|
|
78
|
+
const finalizeAbsentKey = (
|
|
79
|
+
key: string,
|
|
80
|
+
positionalParam: Param<unknown> | undefined,
|
|
81
|
+
namedParam: Param<unknown> | undefined,
|
|
82
|
+
): void => {
|
|
83
|
+
if (finalized.has(key) || seen.has(key)) return;
|
|
84
|
+
finalized.add(key);
|
|
85
|
+
const effective = namedParam ?? positionalParam;
|
|
86
|
+
if (effective === undefined) return;
|
|
87
|
+
if (isOptionalArgType(effective)) {
|
|
88
|
+
if (effective.hasDefault) output[key] = effective.defaultValue;
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
diagnostics.push(
|
|
92
|
+
diagnostic(
|
|
93
|
+
`Attribute "${spec.name}" is missing required argument "${key}"`,
|
|
94
|
+
ctx,
|
|
95
|
+
attributeSpan,
|
|
96
|
+
),
|
|
97
|
+
);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
for (const param of spec.positional) {
|
|
101
|
+
const namedParam = Object.hasOwn(spec.named, param.key) ? spec.named[param.key] : undefined;
|
|
102
|
+
finalizeAbsentKey(param.key, param.type, namedParam);
|
|
103
|
+
}
|
|
104
|
+
for (const key of Object.keys(spec.named)) {
|
|
105
|
+
finalizeAbsentKey(key, undefined, spec.named[key]);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (diagnostics.length > 0) {
|
|
109
|
+
return notOk<readonly PslDiagnostic[]>(diagnostics);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const value = blindCast<
|
|
113
|
+
Out,
|
|
114
|
+
'The engine builds the output object structurally from the spec; TypeScript cannot relate the dynamically-keyed record to the spec-inferred output type.'
|
|
115
|
+
>(output);
|
|
116
|
+
if (spec.refine !== undefined) {
|
|
117
|
+
const refineDiagnostics = spec.refine(value, ctx);
|
|
118
|
+
if (refineDiagnostics.length > 0) {
|
|
119
|
+
return notOk<readonly PslDiagnostic[]>(refineDiagnostics);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return ok(value);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function parseArgValue(
|
|
126
|
+
arg: AttributeArgAst,
|
|
127
|
+
argType: ArgType<unknown>,
|
|
128
|
+
ctx: InterpretCtx,
|
|
129
|
+
diagnostics: PslDiagnostic[],
|
|
130
|
+
): Result<unknown, readonly PslDiagnostic[]> {
|
|
131
|
+
const value = arg.value();
|
|
132
|
+
if (value === undefined) {
|
|
133
|
+
const missing = diagnostic(
|
|
134
|
+
'Attribute argument is missing a value',
|
|
135
|
+
ctx,
|
|
136
|
+
nodePslSpan(arg.syntax, ctx.sourceFile),
|
|
137
|
+
);
|
|
138
|
+
diagnostics.push(missing);
|
|
139
|
+
return notOk<readonly PslDiagnostic[]>([missing]);
|
|
140
|
+
}
|
|
141
|
+
const result = argType.parse(value, ctx);
|
|
142
|
+
if (!result.ok) {
|
|
143
|
+
for (const failure of result.failure) diagnostics.push(failure);
|
|
144
|
+
}
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function isOptionalArgType(param: Param<unknown>): param is OptionalArgType<unknown> {
|
|
149
|
+
return 'optional' in param && param.optional === true;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function diagnostic(message: string, ctx: InterpretCtx, span: PslSpan): PslDiagnostic {
|
|
153
|
+
return { code: ATTRIBUTE_DIAGNOSTIC_CODE, message, sourceId: ctx.sourceId, span };
|
|
154
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { PslDiagnostic } from '@prisma-next/framework-components/psl-ast';
|
|
2
|
+
import type { AttributeOut, AttributeSpec, InterpretCtx, Param, PositionalParam } from './types';
|
|
3
|
+
|
|
4
|
+
interface ModelAttributeConfig<
|
|
5
|
+
Pos extends readonly PositionalParam[],
|
|
6
|
+
Named extends Record<string, Param<unknown>>,
|
|
7
|
+
> {
|
|
8
|
+
readonly positional?: Pos;
|
|
9
|
+
readonly named?: Named;
|
|
10
|
+
readonly refine?: (
|
|
11
|
+
parsed: AttributeOut<Pos, Named>,
|
|
12
|
+
ctx: InterpretCtx,
|
|
13
|
+
) => readonly PslDiagnostic[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function modelAttribute<
|
|
17
|
+
const Pos extends readonly PositionalParam[] = readonly [],
|
|
18
|
+
const Named extends Record<string, Param<unknown>> = Record<never, never>,
|
|
19
|
+
>(name: string, config: ModelAttributeConfig<Pos, Named>): AttributeSpec<AttributeOut<Pos, Named>> {
|
|
20
|
+
return {
|
|
21
|
+
level: 'model',
|
|
22
|
+
name,
|
|
23
|
+
positional: config.positional ?? [],
|
|
24
|
+
named: config.named ?? {},
|
|
25
|
+
...(config.refine !== undefined ? { refine: config.refine } : {}),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ArgType, OptionalArgType } from './types';
|
|
2
|
+
|
|
3
|
+
export function optional<T>(type: ArgType<T>, ...rest: [defaultValue: T] | []): OptionalArgType<T> {
|
|
4
|
+
if (rest.length === 0) {
|
|
5
|
+
return { ...type, optional: true, hasDefault: false };
|
|
6
|
+
}
|
|
7
|
+
return { ...type, optional: true, hasDefault: true, defaultValue: rest[0] };
|
|
8
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { PslDiagnostic } from '@prisma-next/framework-components/psl-ast';
|
|
2
|
+
import type { Result } from '@prisma-next/utils/result';
|
|
3
|
+
import type { Simplify, UnionToIntersection } from '@prisma-next/utils/types';
|
|
4
|
+
import type { SourceFile } from '../source-file';
|
|
5
|
+
import type { FieldSymbol, ModelSymbol } from '../symbol-table';
|
|
6
|
+
import type { ExpressionAst } from '../syntax/ast/expressions';
|
|
7
|
+
|
|
8
|
+
export type AttributeLevel = 'field' | 'model' | 'block';
|
|
9
|
+
|
|
10
|
+
export interface ArgType<T> {
|
|
11
|
+
readonly kind: string;
|
|
12
|
+
readonly label: string;
|
|
13
|
+
// phantom carrier for `T`; never read at runtime.
|
|
14
|
+
readonly _out?: T;
|
|
15
|
+
parse(arg: ExpressionAst, ctx: InterpretCtx): Result<T, readonly PslDiagnostic[]>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface InterpretCtx {
|
|
19
|
+
readonly level: AttributeLevel;
|
|
20
|
+
readonly sourceId: string;
|
|
21
|
+
readonly sourceFile: SourceFile;
|
|
22
|
+
readonly selfModel: ModelSymbol;
|
|
23
|
+
resolveReferencedModel(): ModelSymbol | undefined;
|
|
24
|
+
readonly field?: FieldSymbol;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface OptionalArgType<T> extends ArgType<T> {
|
|
28
|
+
// the engine detects optionality by checking for this marker (`'optional' in param`).
|
|
29
|
+
readonly optional: true;
|
|
30
|
+
readonly hasDefault: boolean;
|
|
31
|
+
readonly defaultValue?: T;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type Param<T> = ArgType<T>;
|
|
35
|
+
|
|
36
|
+
export interface PositionalParam<T = unknown> {
|
|
37
|
+
readonly key: string;
|
|
38
|
+
readonly type: Param<T>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface AttributeSpec<Out> {
|
|
42
|
+
readonly level: AttributeLevel;
|
|
43
|
+
readonly name: string;
|
|
44
|
+
readonly positional: readonly PositionalParam[];
|
|
45
|
+
readonly named: Readonly<Record<string, Param<unknown>>>;
|
|
46
|
+
readonly refine?: (parsed: Out, ctx: InterpretCtx) => readonly PslDiagnostic[];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type OutOf<P> = P extends ArgType<infer T> ? T : never;
|
|
50
|
+
|
|
51
|
+
export type NamedOut<N extends Record<string, Param<unknown>>> = Simplify<
|
|
52
|
+
{ [K in keyof N as N[K] extends OptionalArgType<unknown> ? never : K]: OutOf<N[K]> } & {
|
|
53
|
+
[K in keyof N as N[K] extends OptionalArgType<unknown> ? K : never]?: OutOf<N[K]>;
|
|
54
|
+
}
|
|
55
|
+
>;
|
|
56
|
+
|
|
57
|
+
type PosEntryObject<E extends PositionalParam> =
|
|
58
|
+
E['type'] extends OptionalArgType<unknown>
|
|
59
|
+
? { [K in E['key']]?: OutOf<E['type']> }
|
|
60
|
+
: { [K in E['key']]: OutOf<E['type']> };
|
|
61
|
+
|
|
62
|
+
export type PosOut<Pos extends readonly PositionalParam[]> = Simplify<
|
|
63
|
+
UnionToIntersection<{ [I in keyof Pos]: PosEntryObject<Pos[I]> }[number]>
|
|
64
|
+
>;
|
|
65
|
+
|
|
66
|
+
export type AttributeOut<
|
|
67
|
+
Pos extends readonly PositionalParam[],
|
|
68
|
+
Named extends Record<string, Param<unknown>>,
|
|
69
|
+
> = Simplify<PosOut<Pos> & NamedOut<Named>>;
|
|
70
|
+
|
|
71
|
+
// `S` is unconstrained on purpose: `refine` makes `Out` contravariant, so a bound like `S extends AttributeSpec<unknown>` would reject every spec that uses `refine`.
|
|
72
|
+
export type InferAttr<S> = S extends AttributeSpec<infer Out> ? Out : never;
|
package/src/exports/index.ts
CHANGED
|
@@ -36,6 +36,36 @@ export {
|
|
|
36
36
|
namespacePslExtensionBlocks,
|
|
37
37
|
} from '@prisma-next/framework-components/psl-ast';
|
|
38
38
|
export { getPositionalArgument, parseQuotedStringLiteral } from '../attribute-helpers';
|
|
39
|
+
export { bool } from '../attribute-spec/combinators/bool';
|
|
40
|
+
export { leafDiagnostic } from '../attribute-spec/combinators/diagnostic';
|
|
41
|
+
export { entityRef } from '../attribute-spec/combinators/entity-ref';
|
|
42
|
+
export type { FieldRefArgType, FieldRefScope } from '../attribute-spec/combinators/field-ref';
|
|
43
|
+
export { fieldRef } from '../attribute-spec/combinators/field-ref';
|
|
44
|
+
export { identifier } from '../attribute-spec/combinators/identifier';
|
|
45
|
+
export { int } from '../attribute-spec/combinators/int';
|
|
46
|
+
export type { ListOptions } from '../attribute-spec/combinators/list';
|
|
47
|
+
export { list } from '../attribute-spec/combinators/list';
|
|
48
|
+
export { oneOf } from '../attribute-spec/combinators/one-of';
|
|
49
|
+
export { record } from '../attribute-spec/combinators/record';
|
|
50
|
+
export { str } from '../attribute-spec/combinators/str';
|
|
51
|
+
export { fieldAttribute } from '../attribute-spec/field-attribute';
|
|
52
|
+
export { interpretAttribute } from '../attribute-spec/interpret';
|
|
53
|
+
export { modelAttribute } from '../attribute-spec/model-attribute';
|
|
54
|
+
export { optional } from '../attribute-spec/optional';
|
|
55
|
+
export type {
|
|
56
|
+
ArgType,
|
|
57
|
+
AttributeLevel,
|
|
58
|
+
AttributeOut,
|
|
59
|
+
AttributeSpec,
|
|
60
|
+
InferAttr,
|
|
61
|
+
InterpretCtx,
|
|
62
|
+
NamedOut,
|
|
63
|
+
OptionalArgType,
|
|
64
|
+
OutOf,
|
|
65
|
+
Param,
|
|
66
|
+
PositionalParam,
|
|
67
|
+
PosOut,
|
|
68
|
+
} from '../attribute-spec/types';
|
|
39
69
|
export { findBlockDescriptor, validateExtensionBlockFromSymbol } from '../extension-block';
|
|
40
70
|
export {
|
|
41
71
|
keywordPslSpan,
|
package/src/exports/syntax.ts
CHANGED
|
@@ -7,7 +7,13 @@ export {
|
|
|
7
7
|
FieldAttributeAst,
|
|
8
8
|
ModelAttributeAst,
|
|
9
9
|
} from '../syntax/ast/attributes';
|
|
10
|
-
export type {
|
|
10
|
+
export type {
|
|
11
|
+
AttributeAst,
|
|
12
|
+
BlockMemberAst,
|
|
13
|
+
DeclarationAst,
|
|
14
|
+
GenericBlockMemberAst,
|
|
15
|
+
NamespaceMemberAst,
|
|
16
|
+
} from '../syntax/ast/declarations';
|
|
11
17
|
export {
|
|
12
18
|
CompositeTypeDeclarationAst,
|
|
13
19
|
DocumentAst,
|
|
@@ -35,12 +41,26 @@ export {
|
|
|
35
41
|
export { IdentifierAst } from '../syntax/ast/identifier';
|
|
36
42
|
export { QualifiedNameAst } from '../syntax/ast/qualified-name';
|
|
37
43
|
export { TypeAnnotationAst } from '../syntax/ast/type-annotation';
|
|
38
|
-
export type { AstNode } from '../syntax/ast-helpers';
|
|
39
|
-
export {
|
|
44
|
+
export type { AstNode, BracedBlock } from '../syntax/ast-helpers';
|
|
45
|
+
export {
|
|
46
|
+
any,
|
|
47
|
+
filterChildren,
|
|
48
|
+
findChildToken,
|
|
49
|
+
findFirstChild,
|
|
50
|
+
printSyntax,
|
|
51
|
+
} from '../syntax/ast-helpers';
|
|
40
52
|
export type { GreenElement, GreenNode, GreenToken } from '../syntax/green';
|
|
41
53
|
export { greenNode, greenToken } from '../syntax/green';
|
|
42
54
|
export { GreenNodeBuilder } from '../syntax/green-builder';
|
|
55
|
+
// Navigation helpers
|
|
56
|
+
export type { Direction } from '../syntax/navigation';
|
|
57
|
+
export {
|
|
58
|
+
isTrivia,
|
|
59
|
+
isTriviaKind,
|
|
60
|
+
nonTriviaSibling,
|
|
61
|
+
skipTriviaToken,
|
|
62
|
+
} from '../syntax/navigation';
|
|
43
63
|
// Red layer
|
|
44
|
-
export type { SyntaxElement
|
|
45
|
-
export { createSyntaxTree, SyntaxNode } from '../syntax/red';
|
|
64
|
+
export type { SyntaxElement } from '../syntax/red';
|
|
65
|
+
export { createSyntaxTree, SyntaxNode, SyntaxToken, TokenAtOffset } from '../syntax/red';
|
|
46
66
|
export type { SyntaxKind } from '../syntax/syntax-kind';
|
package/src/interpret.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ContractSourceContext,
|
|
3
|
+
ContractSourceDiagnostic,
|
|
4
|
+
ContractSourceProvider,
|
|
5
|
+
PslContractSourceProvider,
|
|
6
|
+
} from '@prisma-next/config/config-types';
|
|
7
|
+
import type { SourceFile } from './source-file';
|
|
8
|
+
import type { SymbolTable } from './symbol-table';
|
|
9
|
+
import type { DocumentAst } from './syntax/ast/declarations';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Lets editor tooling that already parses incrementally (e.g. the language
|
|
13
|
+
* server) hand cached artifacts to the interpreter instead of forcing a
|
|
14
|
+
* disk re-parse.
|
|
15
|
+
*/
|
|
16
|
+
export interface PslInterpretInput {
|
|
17
|
+
readonly document: DocumentAst;
|
|
18
|
+
readonly sourceFile: SourceFile;
|
|
19
|
+
readonly symbolTable: SymbolTable;
|
|
20
|
+
readonly sourceId: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Declared here — the authoring layer that owns `DocumentAst` / `SourceFile` /
|
|
25
|
+
* `SymbolTable` — because `@prisma-next/config` (core) cannot name authoring
|
|
26
|
+
* types.
|
|
27
|
+
*/
|
|
28
|
+
export interface PslInterpretCapable extends PslContractSourceProvider {
|
|
29
|
+
interpret(
|
|
30
|
+
input: PslInterpretInput,
|
|
31
|
+
context: ContractSourceContext,
|
|
32
|
+
): readonly ContractSourceDiagnostic[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The single seam that narrows a contract source to the interpret capability. */
|
|
36
|
+
export function hasPslInterpreter(source: ContractSourceProvider): source is PslInterpretCapable {
|
|
37
|
+
return (
|
|
38
|
+
source.sourceFormat === 'psl' && 'interpret' in source && typeof source.interpret === 'function'
|
|
39
|
+
);
|
|
40
|
+
}
|
package/src/parse.ts
CHANGED
|
@@ -399,14 +399,24 @@ function parseParenArgs(cursor: Cursor): void {
|
|
|
399
399
|
}
|
|
400
400
|
}
|
|
401
401
|
|
|
402
|
-
export function parseAttributeArg(cursor: Cursor):
|
|
402
|
+
export function parseAttributeArg(cursor: Cursor): void {
|
|
403
|
+
const kind = cursor.peekKind();
|
|
404
|
+
if (
|
|
405
|
+
kind !== 'Ident' &&
|
|
406
|
+
kind !== 'StringLiteral' &&
|
|
407
|
+
kind !== 'NumberLiteral' &&
|
|
408
|
+
kind !== 'LBracket' &&
|
|
409
|
+
kind !== 'LBrace'
|
|
410
|
+
) {
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
403
413
|
cursor.startNode('AttributeArg');
|
|
404
414
|
if (cursor.peekKind() === 'Ident' && cursor.peekKind(1) === 'Colon') {
|
|
405
415
|
parseIdentifier(cursor);
|
|
406
416
|
cursor.bump();
|
|
407
417
|
}
|
|
408
418
|
parseArgValue(cursor);
|
|
409
|
-
|
|
419
|
+
cursor.finishNode();
|
|
410
420
|
}
|
|
411
421
|
|
|
412
422
|
function parseArgValue(cursor: Cursor): void {
|
|
@@ -435,8 +445,16 @@ export function parseAttribute(cursor: Cursor): GreenNode {
|
|
|
435
445
|
return cursor.finishNode();
|
|
436
446
|
}
|
|
437
447
|
|
|
438
|
-
/**
|
|
439
|
-
|
|
448
|
+
/**
|
|
449
|
+
* A type annotation: `QualifiedName (argList)? ([])? (?)?`, e.g.
|
|
450
|
+
* `pgvector.Vector(1536)[]?`. When the field has no type, no node is emitted —
|
|
451
|
+
* a missing type is the absence of a `TypeAnnotation`, not a zero-width one.
|
|
452
|
+
*/
|
|
453
|
+
export function parseTypeAnnotation(cursor: Cursor): void {
|
|
454
|
+
const kind = cursor.peekKind();
|
|
455
|
+
if (kind !== 'Ident' && kind !== 'LBracket' && kind !== 'Question') {
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
440
458
|
cursor.startNode('TypeAnnotation');
|
|
441
459
|
if (cursor.peekKind() === 'Ident') {
|
|
442
460
|
parseQualifiedName(cursor);
|
|
@@ -453,7 +471,7 @@ export function parseTypeAnnotation(cursor: Cursor): GreenNode {
|
|
|
453
471
|
if (cursor.peekKind() === 'Question') {
|
|
454
472
|
cursor.bump();
|
|
455
473
|
}
|
|
456
|
-
|
|
474
|
+
cursor.finishNode();
|
|
457
475
|
}
|
|
458
476
|
|
|
459
477
|
type MemberParser = (cursor: Cursor) => void;
|
package/src/resolve.ts
CHANGED
|
@@ -15,6 +15,7 @@ export interface ResolvedAttributeArg {
|
|
|
15
15
|
readonly kind: 'positional' | 'named';
|
|
16
16
|
readonly name?: string;
|
|
17
17
|
readonly value: string;
|
|
18
|
+
readonly expression?: ExpressionAst;
|
|
18
19
|
readonly span: PslSpan;
|
|
19
20
|
}
|
|
20
21
|
|
|
@@ -69,10 +70,12 @@ function readResolvedArgList(
|
|
|
69
70
|
const args: ResolvedAttributeArg[] = [];
|
|
70
71
|
for (const arg of argList.args()) {
|
|
71
72
|
const name = arg.name()?.name();
|
|
73
|
+
const expression = arg.value();
|
|
72
74
|
args.push({
|
|
73
75
|
kind: name !== undefined ? 'named' : 'positional',
|
|
74
76
|
...(name !== undefined ? { name } : {}),
|
|
75
|
-
value: renderExpression(
|
|
77
|
+
value: renderExpression(expression),
|
|
78
|
+
...(expression !== undefined ? { expression } : {}),
|
|
76
79
|
span: nodePslSpan(arg.syntax, sourceFile),
|
|
77
80
|
});
|
|
78
81
|
}
|
package/src/source-file.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const CARRIAGE_RETURN = 13;
|
|
1
2
|
const LINE_FEED = 10;
|
|
2
3
|
|
|
3
4
|
export interface Position {
|
|
@@ -41,6 +42,30 @@ export class SourceFile {
|
|
|
41
42
|
return this.#lineStarts;
|
|
42
43
|
}
|
|
43
44
|
|
|
45
|
+
lineStartOffset(line: number): number {
|
|
46
|
+
if (line <= 0) {
|
|
47
|
+
return 0;
|
|
48
|
+
}
|
|
49
|
+
return this.#lineStarts[line] ?? this.#text.length;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
lineEndOffset(line: number): number {
|
|
53
|
+
if (line < 0) {
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const nextLineStart = this.#lineStarts[line + 1];
|
|
58
|
+
if (nextLineStart === undefined) {
|
|
59
|
+
return this.#text.length;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const lineFeedOffset = nextLineStart - 1;
|
|
63
|
+
const carriageReturnOffset = lineFeedOffset - 1;
|
|
64
|
+
return this.#text.charCodeAt(carriageReturnOffset) === CARRIAGE_RETURN
|
|
65
|
+
? carriageReturnOffset
|
|
66
|
+
: lineFeedOffset;
|
|
67
|
+
}
|
|
68
|
+
|
|
44
69
|
positionAt(offset: number): Position {
|
|
45
70
|
const clamped = clamp(offset, 0, this.#text.length);
|
|
46
71
|
const line = this.#lineIndexAt(clamped);
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import type { Token } from '../../tokenizer';
|
|
2
1
|
import type { AstNode } from '../ast-helpers';
|
|
3
2
|
import { filterChildren, findChildToken, findFirstChild } from '../ast-helpers';
|
|
4
|
-
import type { SyntaxNode } from '../red';
|
|
3
|
+
import type { SyntaxNode, SyntaxToken } from '../red';
|
|
5
4
|
import { AttributeArgAst } from './expressions';
|
|
6
5
|
import { QualifiedNameAst } from './qualified-name';
|
|
7
6
|
|
|
@@ -12,11 +11,11 @@ export class AttributeArgListAst implements AstNode {
|
|
|
12
11
|
this.syntax = syntax;
|
|
13
12
|
}
|
|
14
13
|
|
|
15
|
-
lparen():
|
|
14
|
+
lparen(): SyntaxToken | undefined {
|
|
16
15
|
return findChildToken(this.syntax, 'LParen');
|
|
17
16
|
}
|
|
18
17
|
|
|
19
|
-
rparen():
|
|
18
|
+
rparen(): SyntaxToken | undefined {
|
|
20
19
|
return findChildToken(this.syntax, 'RParen');
|
|
21
20
|
}
|
|
22
21
|
|
|
@@ -36,7 +35,7 @@ export class FieldAttributeAst implements AstNode {
|
|
|
36
35
|
this.syntax = syntax;
|
|
37
36
|
}
|
|
38
37
|
|
|
39
|
-
at():
|
|
38
|
+
at(): SyntaxToken | undefined {
|
|
40
39
|
return findChildToken(this.syntax, 'At');
|
|
41
40
|
}
|
|
42
41
|
|
|
@@ -60,7 +59,7 @@ export class ModelAttributeAst implements AstNode {
|
|
|
60
59
|
this.syntax = syntax;
|
|
61
60
|
}
|
|
62
61
|
|
|
63
|
-
doubleAt():
|
|
62
|
+
doubleAt(): SyntaxToken | undefined {
|
|
64
63
|
return findChildToken(this.syntax, 'DoubleAt');
|
|
65
64
|
}
|
|
66
65
|
|