@prisma-next/psl-parser 0.14.0 → 0.15.0-dev.1
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/README.md +28 -10
- package/dist/declarations-DR6To8_k.mjs +1060 -0
- package/dist/declarations-DR6To8_k.mjs.map +1 -0
- package/dist/format.d.mts +19 -0
- package/dist/format.d.mts.map +1 -0
- package/dist/format.mjs +470 -0
- package/dist/format.mjs.map +1 -0
- package/dist/index.d.mts +144 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +849 -3
- package/dist/index.mjs.map +1 -1
- package/dist/interpret.d.mts +39 -0
- package/dist/interpret.d.mts.map +1 -0
- package/dist/interpret.mjs +25 -0
- package/dist/interpret.mjs.map +1 -0
- package/dist/parse-BazJr7Ye.d.mts +426 -0
- package/dist/parse-BazJr7Ye.d.mts.map +1 -0
- package/dist/parse-CdeXr0T3.mjs +626 -0
- 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 +21 -347
- package/dist/syntax.d.mts.map +1 -1
- package/dist/syntax.mjs +26 -1401
- package/dist/syntax.mjs.map +1 -1
- package/package.json +11 -8
- 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/func-call.ts +65 -0
- package/src/attribute-spec/combinators/identifier.ts +16 -0
- package/src/attribute-spec/combinators/int.ts +35 -0
- package/src/attribute-spec/combinators/list.ts +43 -0
- package/src/attribute-spec/combinators/num.ts +26 -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 +177 -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 +140 -0
- package/src/exports/format.ts +3 -0
- package/src/exports/index.ts +61 -3
- package/src/exports/interpret.ts +2 -0
- package/src/exports/syntax.ts +25 -5
- package/src/extension-block.ts +107 -0
- package/src/format/emit.ts +603 -0
- package/src/format/error.ts +13 -0
- package/src/format/format.ts +13 -0
- package/src/format/options.ts +28 -0
- package/src/interpret.ts +67 -0
- package/src/parse.ts +32 -5
- package/src/resolve.ts +123 -0
- package/src/source-file.ts +25 -0
- package/src/symbol-table.ts +446 -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/parser-CaplKvRs.mjs +0 -1145
- package/dist/parser-CaplKvRs.mjs.map +0 -1
- package/dist/parser-Dfi3Wfdq.d.mts +0 -7
- package/dist/parser-Dfi3Wfdq.d.mts.map +0 -1
- package/dist/parser.d.mts +0 -2
- package/dist/parser.mjs +0 -2
- package/src/exports/parser.ts +0 -1
- package/src/parser.ts +0 -1642
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { PslDiagnostic } from '@prisma-next/framework-components/psl-ast';
|
|
2
|
+
import { notOk, ok, type Result } from '@prisma-next/utils/result';
|
|
3
|
+
import { StringLiteralExprAst } from '../../syntax/ast/expressions';
|
|
4
|
+
import type { ArgType } from '../types';
|
|
5
|
+
import { leafDiagnostic } from './diagnostic';
|
|
6
|
+
|
|
7
|
+
export function str(): ArgType<string> {
|
|
8
|
+
return {
|
|
9
|
+
kind: 'str',
|
|
10
|
+
label: 'string',
|
|
11
|
+
parse: (arg, ctx): Result<string, readonly PslDiagnostic[]> => {
|
|
12
|
+
if (arg instanceof StringLiteralExprAst) {
|
|
13
|
+
const value = arg.value();
|
|
14
|
+
if (value !== undefined) return ok(value);
|
|
15
|
+
}
|
|
16
|
+
return notOk([leafDiagnostic(ctx, arg, 'Expected a string literal')]);
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -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 FieldAttributeConfig<
|
|
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 fieldAttribute<
|
|
17
|
+
const Pos extends readonly PositionalParam[] = readonly [],
|
|
18
|
+
const Named extends Record<string, Param<unknown>> = Record<never, never>,
|
|
19
|
+
>(name: string, config: FieldAttributeConfig<Pos, Named>): AttributeSpec<AttributeOut<Pos, Named>> {
|
|
20
|
+
return {
|
|
21
|
+
level: 'field',
|
|
22
|
+
name,
|
|
23
|
+
positional: config.positional ?? [],
|
|
24
|
+
named: config.named ?? {},
|
|
25
|
+
...(config.refine !== undefined ? { refine: config.refine } : {}),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
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 {
|
|
9
|
+
ArgType,
|
|
10
|
+
AttributeSpec,
|
|
11
|
+
InterpretCtx,
|
|
12
|
+
OptionalArgType,
|
|
13
|
+
Param,
|
|
14
|
+
PositionalParam,
|
|
15
|
+
} from './types';
|
|
16
|
+
|
|
17
|
+
// The positional/named argument-binding for an attribute or a function call. `name` labels the
|
|
18
|
+
// callee in binding diagnostics (`Attribute "<name>" …`); `span` anchors the arity diagnostics
|
|
19
|
+
// (too-many / missing) that have no per-argument node to point at.
|
|
20
|
+
export interface ArgBindingSpec {
|
|
21
|
+
readonly name: string;
|
|
22
|
+
readonly positional: readonly PositionalParam<unknown>[];
|
|
23
|
+
readonly named: Readonly<Record<string, Param<unknown>>>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function interpretArgs(
|
|
27
|
+
args: Iterable<AttributeArgAst>,
|
|
28
|
+
spec: ArgBindingSpec,
|
|
29
|
+
ctx: InterpretCtx,
|
|
30
|
+
span: PslSpan,
|
|
31
|
+
): Result<Record<string, unknown>, readonly PslDiagnostic[]> {
|
|
32
|
+
const diagnostics: PslDiagnostic[] = [];
|
|
33
|
+
|
|
34
|
+
const output: Record<string, unknown> = {};
|
|
35
|
+
const seen = new Set<string>();
|
|
36
|
+
let positionalSlot = 0;
|
|
37
|
+
let reportedExcess = false;
|
|
38
|
+
|
|
39
|
+
for (const arg of args) {
|
|
40
|
+
const name = arg.name()?.name();
|
|
41
|
+
|
|
42
|
+
let key: string;
|
|
43
|
+
let param: Param<unknown>;
|
|
44
|
+
if (name === undefined) {
|
|
45
|
+
const posParam = spec.positional[positionalSlot];
|
|
46
|
+
if (posParam === undefined) {
|
|
47
|
+
if (!reportedExcess) {
|
|
48
|
+
diagnostics.push(
|
|
49
|
+
diagnostic(
|
|
50
|
+
`Attribute "${spec.name}" received too many positional arguments`,
|
|
51
|
+
ctx,
|
|
52
|
+
span,
|
|
53
|
+
),
|
|
54
|
+
);
|
|
55
|
+
reportedExcess = true;
|
|
56
|
+
}
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
positionalSlot += 1;
|
|
60
|
+
key = posParam.key;
|
|
61
|
+
param = posParam.type;
|
|
62
|
+
} else {
|
|
63
|
+
const namedParam = Object.hasOwn(spec.named, name) ? spec.named[name] : undefined;
|
|
64
|
+
if (namedParam === undefined) {
|
|
65
|
+
diagnostics.push(
|
|
66
|
+
diagnostic(
|
|
67
|
+
`Attribute "${spec.name}" received unknown argument "${name}"`,
|
|
68
|
+
ctx,
|
|
69
|
+
nodePslSpan(arg.syntax, ctx.sourceFile),
|
|
70
|
+
),
|
|
71
|
+
);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
key = name;
|
|
75
|
+
param = namedParam;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (seen.has(key)) {
|
|
79
|
+
diagnostics.push(
|
|
80
|
+
diagnostic(
|
|
81
|
+
`Attribute "${spec.name}" received duplicate argument "${key}"`,
|
|
82
|
+
ctx,
|
|
83
|
+
nodePslSpan(arg.syntax, ctx.sourceFile),
|
|
84
|
+
),
|
|
85
|
+
);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
seen.add(key);
|
|
89
|
+
const result = parseArgValue(arg, param, ctx, diagnostics);
|
|
90
|
+
if (result.ok) output[key] = result.value;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const finalized = new Set<string>();
|
|
94
|
+
const finalizeAbsentKey = (
|
|
95
|
+
key: string,
|
|
96
|
+
positionalParam: Param<unknown> | undefined,
|
|
97
|
+
namedParam: Param<unknown> | undefined,
|
|
98
|
+
): void => {
|
|
99
|
+
if (finalized.has(key) || seen.has(key)) return;
|
|
100
|
+
finalized.add(key);
|
|
101
|
+
const effective = namedParam ?? positionalParam;
|
|
102
|
+
if (effective === undefined) return;
|
|
103
|
+
if (isOptionalArgType(effective)) {
|
|
104
|
+
if (effective.hasDefault) output[key] = effective.defaultValue;
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
diagnostics.push(
|
|
108
|
+
diagnostic(`Attribute "${spec.name}" is missing required argument "${key}"`, ctx, span),
|
|
109
|
+
);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
for (const param of spec.positional) {
|
|
113
|
+
const namedParam = Object.hasOwn(spec.named, param.key) ? spec.named[param.key] : undefined;
|
|
114
|
+
finalizeAbsentKey(param.key, param.type, namedParam);
|
|
115
|
+
}
|
|
116
|
+
for (const key of Object.keys(spec.named)) {
|
|
117
|
+
finalizeAbsentKey(key, undefined, spec.named[key]);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (diagnostics.length > 0) {
|
|
121
|
+
return notOk<readonly PslDiagnostic[]>(diagnostics);
|
|
122
|
+
}
|
|
123
|
+
return ok(output);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function interpretAttribute<Out>(
|
|
127
|
+
attrNode: FieldAttributeAst | ModelAttributeAst,
|
|
128
|
+
spec: AttributeSpec<Out>,
|
|
129
|
+
ctx: InterpretCtx,
|
|
130
|
+
): Result<Out, readonly PslDiagnostic[]> {
|
|
131
|
+
const attributeSpan = nodePslSpan(attrNode.syntax, ctx.sourceFile);
|
|
132
|
+
const bound = interpretArgs(attrNode.argList()?.args() ?? [], spec, ctx, attributeSpan);
|
|
133
|
+
if (!bound.ok) return notOk<readonly PslDiagnostic[]>(bound.failure);
|
|
134
|
+
|
|
135
|
+
const value = blindCast<
|
|
136
|
+
Out,
|
|
137
|
+
'The engine builds the output object structurally from the spec; TypeScript cannot relate the dynamically-keyed record to the spec-inferred output type.'
|
|
138
|
+
>(bound.value);
|
|
139
|
+
if (spec.refine !== undefined) {
|
|
140
|
+
const refineDiagnostics = spec.refine(value, ctx);
|
|
141
|
+
if (refineDiagnostics.length > 0) {
|
|
142
|
+
return notOk<readonly PslDiagnostic[]>(refineDiagnostics);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return ok(value);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function parseArgValue(
|
|
149
|
+
arg: AttributeArgAst,
|
|
150
|
+
argType: ArgType<unknown>,
|
|
151
|
+
ctx: InterpretCtx,
|
|
152
|
+
diagnostics: PslDiagnostic[],
|
|
153
|
+
): Result<unknown, readonly PslDiagnostic[]> {
|
|
154
|
+
const value = arg.value();
|
|
155
|
+
if (value === undefined) {
|
|
156
|
+
const missing = diagnostic(
|
|
157
|
+
'Attribute argument is missing a value',
|
|
158
|
+
ctx,
|
|
159
|
+
nodePslSpan(arg.syntax, ctx.sourceFile),
|
|
160
|
+
);
|
|
161
|
+
diagnostics.push(missing);
|
|
162
|
+
return notOk<readonly PslDiagnostic[]>([missing]);
|
|
163
|
+
}
|
|
164
|
+
const result = argType.parse(value, ctx);
|
|
165
|
+
if (!result.ok) {
|
|
166
|
+
for (const failure of result.failure) diagnostics.push(failure);
|
|
167
|
+
}
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function isOptionalArgType(param: Param<unknown>): param is OptionalArgType<unknown> {
|
|
172
|
+
return 'optional' in param && param.optional === true;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function diagnostic(message: string, ctx: InterpretCtx, span: PslSpan): PslDiagnostic {
|
|
176
|
+
return { code: ATTRIBUTE_DIAGNOSTIC_CODE, message, sourceId: ctx.sourceId, span };
|
|
177
|
+
}
|
|
@@ -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;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import type { AuthoringPslBlockDescriptor } from '@prisma-next/framework-components/authoring';
|
|
2
|
+
import type {
|
|
3
|
+
PslBlockParam,
|
|
4
|
+
PslExtensionBlock,
|
|
5
|
+
PslExtensionBlockAttribute,
|
|
6
|
+
PslExtensionBlockParamValue,
|
|
7
|
+
PslSpan,
|
|
8
|
+
} from '@prisma-next/framework-components/psl-ast';
|
|
9
|
+
import type { ParseDiagnostic } from './parse';
|
|
10
|
+
import { nodePslSpan } from './resolve';
|
|
11
|
+
import type { SourceFile } from './source-file';
|
|
12
|
+
import type { GenericBlockDeclarationAst, KeyValuePairAst } from './syntax/ast/declarations';
|
|
13
|
+
import { ArrayLiteralAst, type ExpressionAst } from './syntax/ast/expressions';
|
|
14
|
+
import { printSyntax } from './syntax/ast-helpers';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Descriptor-free and unknown parameters become `value` stubs so validation can
|
|
18
|
+
* report them via key-set comparison. Duplicate member names are first-wins.
|
|
19
|
+
*/
|
|
20
|
+
export function reconstructExtensionBlock(
|
|
21
|
+
node: GenericBlockDeclarationAst,
|
|
22
|
+
descriptor: AuthoringPslBlockDescriptor | undefined,
|
|
23
|
+
sourceFile: SourceFile,
|
|
24
|
+
diagnostics: ParseDiagnostic[],
|
|
25
|
+
): PslExtensionBlock {
|
|
26
|
+
const keyword = node.keyword()?.text ?? '';
|
|
27
|
+
const blockName = node.name()?.name() ?? '';
|
|
28
|
+
|
|
29
|
+
const blockAttributes: PslExtensionBlockAttribute[] = [];
|
|
30
|
+
for (const attribute of node.attributes()) {
|
|
31
|
+
const name = attribute.name()?.path().join('.') ?? '';
|
|
32
|
+
const args = Array.from(attribute.argList()?.args() ?? [], (arg) => {
|
|
33
|
+
const value = arg.value();
|
|
34
|
+
return {
|
|
35
|
+
kind: 'positional' as const,
|
|
36
|
+
value: value === undefined ? '' : printSyntax(value.syntax).trim(),
|
|
37
|
+
span: nodePslSpan(arg.syntax, sourceFile),
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
blockAttributes.push({
|
|
41
|
+
name,
|
|
42
|
+
args,
|
|
43
|
+
span: nodePslSpan(attribute.syntax, sourceFile),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const parameters: Record<string, PslExtensionBlockParamValue> = {};
|
|
48
|
+
for (const entry of node.entries()) {
|
|
49
|
+
const key = entry.key()?.name();
|
|
50
|
+
if (key === undefined) continue;
|
|
51
|
+
const span = nodePslSpan(entry.syntax, sourceFile);
|
|
52
|
+
if (Object.hasOwn(parameters, key)) {
|
|
53
|
+
diagnostics.push({
|
|
54
|
+
code: 'PSL_EXTENSION_DUPLICATE_PARAMETER',
|
|
55
|
+
message: `Duplicate parameter "${key}" in "${keyword}" block "${blockName}"; first occurrence wins`,
|
|
56
|
+
range: {
|
|
57
|
+
start: sourceFile.positionAt(entry.syntax.offset),
|
|
58
|
+
end: sourceFile.positionAt(entry.syntax.offset + entry.syntax.green.textLength),
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
parameters[key] = reconstructParamValue(
|
|
64
|
+
entry,
|
|
65
|
+
descriptor?.parameters[key],
|
|
66
|
+
span,
|
|
67
|
+
sourceFile,
|
|
68
|
+
diagnostics,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
kind: descriptor?.discriminator ?? keyword,
|
|
74
|
+
keyword,
|
|
75
|
+
name: blockName,
|
|
76
|
+
parameters,
|
|
77
|
+
blockAttributes,
|
|
78
|
+
span: nodePslSpan(node.syntax, sourceFile),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function reconstructParamValue(
|
|
83
|
+
entry: KeyValuePairAst,
|
|
84
|
+
param: PslBlockParam | undefined,
|
|
85
|
+
span: PslSpan,
|
|
86
|
+
sourceFile: SourceFile,
|
|
87
|
+
diagnostics: ParseDiagnostic[],
|
|
88
|
+
): PslExtensionBlockParamValue {
|
|
89
|
+
const value = entry.value();
|
|
90
|
+
if (value === undefined) {
|
|
91
|
+
return { kind: 'bare', span };
|
|
92
|
+
}
|
|
93
|
+
return reconstructFromExpression(value, param, span, sourceFile, diagnostics);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function reconstructFromExpression(
|
|
97
|
+
value: ExpressionAst,
|
|
98
|
+
param: PslBlockParam | undefined,
|
|
99
|
+
span: PslSpan,
|
|
100
|
+
sourceFile: SourceFile,
|
|
101
|
+
diagnostics?: ParseDiagnostic[],
|
|
102
|
+
): PslExtensionBlockParamValue {
|
|
103
|
+
const raw = printSyntax(value.syntax).trim();
|
|
104
|
+
if (param?.kind === 'list') {
|
|
105
|
+
const array = ArrayLiteralAst.cast(value.syntax);
|
|
106
|
+
if (!array) {
|
|
107
|
+
diagnostics?.push({
|
|
108
|
+
code: 'PSL_EXTENSION_INVALID_VALUE',
|
|
109
|
+
message: `List parameter expects an array literal, got ${raw}`,
|
|
110
|
+
range: {
|
|
111
|
+
start: sourceFile.positionAt(value.syntax.offset),
|
|
112
|
+
end: sourceFile.positionAt(value.syntax.offset + value.syntax.green.textLength),
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
return { kind: 'value', raw, span };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const items: PslExtensionBlockParamValue[] = [];
|
|
119
|
+
for (const element of array.elements()) {
|
|
120
|
+
items.push(
|
|
121
|
+
reconstructFromExpression(
|
|
122
|
+
element,
|
|
123
|
+
param.of,
|
|
124
|
+
nodePslSpan(element.syntax, sourceFile),
|
|
125
|
+
sourceFile,
|
|
126
|
+
diagnostics,
|
|
127
|
+
),
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
return { kind: 'list', items, span };
|
|
131
|
+
}
|
|
132
|
+
switch (param?.kind) {
|
|
133
|
+
case 'ref':
|
|
134
|
+
return { kind: 'ref', identifier: raw, span };
|
|
135
|
+
case 'option':
|
|
136
|
+
return { kind: 'option', token: raw, span };
|
|
137
|
+
default:
|
|
138
|
+
return { kind: 'value', raw, span };
|
|
139
|
+
}
|
|
140
|
+
}
|
package/src/exports/index.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
export type {
|
|
2
|
-
ParsePslDocumentInput,
|
|
3
|
-
ParsePslDocumentResult,
|
|
4
2
|
PslAttribute,
|
|
5
3
|
PslAttributeArgument,
|
|
6
4
|
PslAttributeNamedArgument,
|
|
@@ -38,4 +36,64 @@ export {
|
|
|
38
36
|
namespacePslExtensionBlocks,
|
|
39
37
|
} from '@prisma-next/framework-components/psl-ast';
|
|
40
38
|
export { getPositionalArgument, parseQuotedStringLiteral } from '../attribute-helpers';
|
|
41
|
-
export {
|
|
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 type { FuncCallSig, TypedFuncCall } from '../attribute-spec/combinators/func-call';
|
|
45
|
+
export { funcCall } from '../attribute-spec/combinators/func-call';
|
|
46
|
+
export { identifier } from '../attribute-spec/combinators/identifier';
|
|
47
|
+
export { int } from '../attribute-spec/combinators/int';
|
|
48
|
+
export type { ListOptions } from '../attribute-spec/combinators/list';
|
|
49
|
+
export { list } from '../attribute-spec/combinators/list';
|
|
50
|
+
export { num } from '../attribute-spec/combinators/num';
|
|
51
|
+
export { oneOf } from '../attribute-spec/combinators/one-of';
|
|
52
|
+
export { record } from '../attribute-spec/combinators/record';
|
|
53
|
+
export { str } from '../attribute-spec/combinators/str';
|
|
54
|
+
export { fieldAttribute } from '../attribute-spec/field-attribute';
|
|
55
|
+
export type { ArgBindingSpec } from '../attribute-spec/interpret';
|
|
56
|
+
export { interpretArgs, interpretAttribute } from '../attribute-spec/interpret';
|
|
57
|
+
export { modelAttribute } from '../attribute-spec/model-attribute';
|
|
58
|
+
export { optional } from '../attribute-spec/optional';
|
|
59
|
+
export type {
|
|
60
|
+
ArgType,
|
|
61
|
+
AttributeLevel,
|
|
62
|
+
AttributeOut,
|
|
63
|
+
AttributeSpec,
|
|
64
|
+
InferAttr,
|
|
65
|
+
InterpretCtx,
|
|
66
|
+
NamedOut,
|
|
67
|
+
OptionalArgType,
|
|
68
|
+
OutOf,
|
|
69
|
+
Param,
|
|
70
|
+
PositionalParam,
|
|
71
|
+
PosOut,
|
|
72
|
+
} from '../attribute-spec/types';
|
|
73
|
+
export { findBlockDescriptor, validateExtensionBlockFromSymbol } from '../extension-block';
|
|
74
|
+
export {
|
|
75
|
+
keywordPslSpan,
|
|
76
|
+
nodePslSpan,
|
|
77
|
+
rangeToPslSpan,
|
|
78
|
+
readResolvedAttribute,
|
|
79
|
+
readResolvedAttributes,
|
|
80
|
+
readResolvedConstructorCall,
|
|
81
|
+
} from '../resolve';
|
|
82
|
+
export type {
|
|
83
|
+
BlockSymbol,
|
|
84
|
+
BuildSymbolTableOptions,
|
|
85
|
+
CompositeTypeSymbol,
|
|
86
|
+
FieldSymbol,
|
|
87
|
+
ModelSymbol,
|
|
88
|
+
NamespaceSymbol,
|
|
89
|
+
ResolvedAttribute,
|
|
90
|
+
ResolvedAttributeArg,
|
|
91
|
+
ResolvedNamedTypeBinding,
|
|
92
|
+
ResolvedTypeConstructorCall,
|
|
93
|
+
ScalarSymbol,
|
|
94
|
+
SymbolTable,
|
|
95
|
+
SymbolTableResult,
|
|
96
|
+
TopLevelScope,
|
|
97
|
+
TypeAliasSymbol,
|
|
98
|
+
} from '../symbol-table';
|
|
99
|
+
export { buildSymbolTable } from '../symbol-table';
|
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';
|