@prisma-next/psl-parser 0.14.0-dev.4 → 0.14.0-dev.40

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.
Files changed (47) hide show
  1. package/README.md +28 -10
  2. package/dist/declarations-CPDuQm7x.mjs +1055 -0
  3. package/dist/declarations-CPDuQm7x.mjs.map +1 -0
  4. package/dist/format.d.mts +1 -1
  5. package/dist/format.mjs +2 -1
  6. package/dist/format.mjs.map +1 -1
  7. package/dist/index.d.mts +137 -3
  8. package/dist/index.d.mts.map +1 -1
  9. package/dist/index.mjs +474 -3
  10. package/dist/index.mjs.map +1 -1
  11. package/dist/{parse-BjZ1LPe6.d.mts → parse-BKN_-hW8.d.mts} +126 -51
  12. package/dist/parse-BKN_-hW8.d.mts.map +1 -0
  13. package/dist/parse-Du30Ca0-.mjs +626 -0
  14. package/dist/parse-Du30Ca0-.mjs.map +1 -0
  15. package/dist/syntax.d.mts +20 -2
  16. package/dist/syntax.d.mts.map +1 -1
  17. package/dist/syntax.mjs +43 -2
  18. package/dist/syntax.mjs.map +1 -0
  19. package/package.json +5 -6
  20. package/src/block-reconstruction.ts +139 -0
  21. package/src/exports/index.ts +27 -3
  22. package/src/exports/syntax.ts +25 -5
  23. package/src/extension-block.ts +107 -0
  24. package/src/parse.ts +23 -5
  25. package/src/resolve.ts +123 -0
  26. package/src/source-file.ts +25 -0
  27. package/src/symbol-table.ts +446 -0
  28. package/src/syntax/ast/attributes.ts +5 -6
  29. package/src/syntax/ast/declarations.ts +51 -26
  30. package/src/syntax/ast/expressions.ts +12 -13
  31. package/src/syntax/ast/identifier.ts +2 -3
  32. package/src/syntax/ast/qualified-name.ts +22 -19
  33. package/src/syntax/ast/type-annotation.ts +4 -5
  34. package/src/syntax/ast-helpers.ts +27 -3
  35. package/src/syntax/navigation.ts +55 -0
  36. package/src/syntax/red.ts +317 -42
  37. package/dist/parse-B_3gIEFd.mjs +0 -1421
  38. package/dist/parse-B_3gIEFd.mjs.map +0 -1
  39. package/dist/parse-BjZ1LPe6.d.mts.map +0 -1
  40. package/dist/parser-CaplKvRs.mjs +0 -1145
  41. package/dist/parser-CaplKvRs.mjs.map +0 -1
  42. package/dist/parser-Dfi3Wfdq.d.mts +0 -7
  43. package/dist/parser-Dfi3Wfdq.d.mts.map +0 -1
  44. package/dist/parser.d.mts +0 -2
  45. package/dist/parser.mjs +0 -2
  46. package/src/exports/parser.ts +0 -1
  47. package/src/parser.ts +0 -1642
package/src/resolve.ts ADDED
@@ -0,0 +1,123 @@
1
+ import type { PslSpan } from '@prisma-next/framework-components/psl-ast';
2
+ import type { Position, Range, SourceFile } from './source-file';
3
+ import type {
4
+ AttributeArgListAst,
5
+ FieldAttributeAst,
6
+ ModelAttributeAst,
7
+ } from './syntax/ast/attributes';
8
+ import type { ExpressionAst } from './syntax/ast/expressions';
9
+ import type { QualifiedNameAst } from './syntax/ast/qualified-name';
10
+ import type { TypeAnnotationAst } from './syntax/ast/type-annotation';
11
+ import { printSyntax } from './syntax/ast-helpers';
12
+ import type { SyntaxNode } from './syntax/red';
13
+
14
+ export interface ResolvedAttributeArg {
15
+ readonly kind: 'positional' | 'named';
16
+ readonly name?: string;
17
+ readonly value: string;
18
+ readonly expression?: ExpressionAst;
19
+ readonly span: PslSpan;
20
+ }
21
+
22
+ export interface ResolvedAttribute {
23
+ readonly name: string;
24
+ readonly args: readonly ResolvedAttributeArg[];
25
+ readonly span: PslSpan;
26
+ }
27
+
28
+ export interface ResolvedTypeConstructorCall {
29
+ readonly path: readonly string[];
30
+ readonly args: readonly ResolvedAttributeArg[];
31
+ readonly span: PslSpan;
32
+ }
33
+
34
+ export function readResolvedAttribute(
35
+ attribute: FieldAttributeAst | ModelAttributeAst,
36
+ sourceFile: SourceFile,
37
+ ): ResolvedAttribute {
38
+ return {
39
+ name: attributeName(attribute.name()),
40
+ args: readResolvedArgList(attribute.argList(), sourceFile),
41
+ span: nodePslSpan(attribute.syntax, sourceFile),
42
+ };
43
+ }
44
+
45
+ export function readResolvedAttributes(
46
+ attributes: Iterable<FieldAttributeAst | ModelAttributeAst>,
47
+ sourceFile: SourceFile,
48
+ ): readonly ResolvedAttribute[] {
49
+ return Array.from(attributes, (attribute) => readResolvedAttribute(attribute, sourceFile));
50
+ }
51
+
52
+ export function readResolvedConstructorCall(
53
+ annotation: TypeAnnotationAst | undefined,
54
+ sourceFile: SourceFile,
55
+ ): ResolvedTypeConstructorCall | undefined {
56
+ const argList = annotation?.argList();
57
+ if (annotation === undefined || argList === undefined) return undefined;
58
+ return {
59
+ path: annotation.name()?.path() ?? [],
60
+ args: readResolvedArgList(argList, sourceFile),
61
+ span: nodePslSpan(annotation.syntax, sourceFile),
62
+ };
63
+ }
64
+
65
+ function readResolvedArgList(
66
+ argList: AttributeArgListAst | undefined,
67
+ sourceFile: SourceFile,
68
+ ): readonly ResolvedAttributeArg[] {
69
+ if (argList === undefined) return [];
70
+ const args: ResolvedAttributeArg[] = [];
71
+ for (const arg of argList.args()) {
72
+ const name = arg.name()?.name();
73
+ const expression = arg.value();
74
+ args.push({
75
+ kind: name !== undefined ? 'named' : 'positional',
76
+ ...(name !== undefined ? { name } : {}),
77
+ value: renderExpression(expression),
78
+ ...(expression !== undefined ? { expression } : {}),
79
+ span: nodePslSpan(arg.syntax, sourceFile),
80
+ });
81
+ }
82
+ return args;
83
+ }
84
+
85
+ function attributeName(name: QualifiedNameAst | undefined): string {
86
+ return name?.path().join('.') ?? '';
87
+ }
88
+
89
+ function renderExpression(expression: ExpressionAst | undefined): string {
90
+ if (expression === undefined) return '';
91
+ return printSyntax(expression.syntax).trim();
92
+ }
93
+
94
+ export function nodePslSpan(node: SyntaxNode, sourceFile: SourceFile): PslSpan {
95
+ const start = node.offset;
96
+ const end = start + node.green.textLength;
97
+ return {
98
+ start: offsetToPslPosition(start, sourceFile),
99
+ end: offsetToPslPosition(end, sourceFile),
100
+ };
101
+ }
102
+
103
+ /** Unsupported-top-level-block diagnostics are anchored to the keyword token. */
104
+ export function keywordPslSpan(node: SyntaxNode, keyword: string, sourceFile: SourceFile): PslSpan {
105
+ const start = node.offset;
106
+ const end = start + keyword.length;
107
+ return {
108
+ start: offsetToPslPosition(start, sourceFile),
109
+ end: offsetToPslPosition(end, sourceFile),
110
+ };
111
+ }
112
+
113
+ export function rangeToPslSpan(range: Range, sourceFile: SourceFile): PslSpan {
114
+ return {
115
+ start: offsetToPslPosition(sourceFile.offsetAt(range.start), sourceFile),
116
+ end: offsetToPslPosition(sourceFile.offsetAt(range.end), sourceFile),
117
+ };
118
+ }
119
+
120
+ function offsetToPslPosition(offset: number, sourceFile: SourceFile): PslSpan['start'] {
121
+ const position: Position = sourceFile.positionAt(offset);
122
+ return { offset, line: position.line + 1, column: position.character + 1 };
123
+ }
@@ -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);
@@ -0,0 +1,446 @@
1
+ import type { AuthoringPslBlockDescriptorNamespace } from '@prisma-next/framework-components/authoring';
2
+ import type { PslExtensionBlock, PslSpan } from '@prisma-next/framework-components/psl-ast';
3
+ import { reconstructExtensionBlock } from './block-reconstruction';
4
+ import { findBlockDescriptor } from './extension-block';
5
+ import type { ParseDiagnostic } from './parse';
6
+ import {
7
+ nodePslSpan,
8
+ type ResolvedAttribute,
9
+ type ResolvedTypeConstructorCall,
10
+ readResolvedAttributes,
11
+ readResolvedConstructorCall,
12
+ } from './resolve';
13
+ import type { Range, SourceFile } from './source-file';
14
+ import {
15
+ CompositeTypeDeclarationAst,
16
+ type DocumentAst,
17
+ type FieldDeclarationAst,
18
+ GenericBlockDeclarationAst,
19
+ ModelDeclarationAst,
20
+ type NamedTypeDeclarationAst,
21
+ NamespaceDeclarationAst,
22
+ TypesBlockAst,
23
+ } from './syntax/ast/declarations';
24
+ import type { IdentifierAst } from './syntax/ast/identifier';
25
+ import type { SyntaxNode } from './syntax/red';
26
+
27
+ export type {
28
+ ResolvedAttribute,
29
+ ResolvedAttributeArg,
30
+ ResolvedTypeConstructorCall,
31
+ } from './resolve';
32
+
33
+ export interface SymbolTable {
34
+ readonly topLevel: TopLevelScope;
35
+ }
36
+
37
+ export interface TopLevelScope {
38
+ readonly namespaces: Record<string, NamespaceSymbol>;
39
+ readonly scalars: Record<string, ScalarSymbol>;
40
+ readonly typeAliases: Record<string, TypeAliasSymbol>;
41
+ readonly blocks: Record<string, BlockSymbol>;
42
+ readonly models: Record<string, ModelSymbol>;
43
+ readonly compositeTypes: Record<string, CompositeTypeSymbol>;
44
+ }
45
+
46
+ export interface NamespaceSymbol {
47
+ readonly kind: 'namespace';
48
+ readonly name: string;
49
+ readonly node: NamespaceDeclarationAst;
50
+ readonly span: PslSpan;
51
+ readonly models: Record<string, ModelSymbol>;
52
+ readonly compositeTypes: Record<string, CompositeTypeSymbol>;
53
+ readonly blocks: Record<string, BlockSymbol>;
54
+ }
55
+
56
+ export interface ModelSymbol {
57
+ readonly kind: 'model';
58
+ readonly name: string;
59
+ readonly node: ModelDeclarationAst;
60
+ readonly span: PslSpan;
61
+ readonly fields: Record<string, FieldSymbol>;
62
+ readonly attributes: readonly ResolvedAttribute[];
63
+ }
64
+
65
+ export interface CompositeTypeSymbol {
66
+ readonly kind: 'compositeType';
67
+ readonly name: string;
68
+ readonly node: CompositeTypeDeclarationAst;
69
+ readonly span: PslSpan;
70
+ readonly fields: Record<string, FieldSymbol>;
71
+ readonly attributes: readonly ResolvedAttribute[];
72
+ }
73
+
74
+ export interface BlockSymbol {
75
+ readonly kind: 'block';
76
+ readonly name: string;
77
+ readonly keyword: string;
78
+ readonly node: GenericBlockDeclarationAst;
79
+ readonly span: PslSpan;
80
+ /** Resolved once so consumers do not independently classify block parameters. */
81
+ readonly block: PslExtensionBlock;
82
+ }
83
+
84
+ export interface ResolvedNamedTypeBinding {
85
+ readonly baseType?: string;
86
+ readonly typeConstructor?: ResolvedTypeConstructorCall;
87
+ readonly isConstructor: boolean;
88
+ readonly attributes: readonly ResolvedAttribute[];
89
+ }
90
+
91
+ export interface ScalarSymbol extends ResolvedNamedTypeBinding {
92
+ readonly kind: 'scalar';
93
+ readonly name: string;
94
+ readonly node: NamedTypeDeclarationAst;
95
+ readonly span: PslSpan;
96
+ }
97
+
98
+ export interface TypeAliasSymbol extends ResolvedNamedTypeBinding {
99
+ readonly kind: 'typeAlias';
100
+ readonly name: string;
101
+ readonly node: NamedTypeDeclarationAst;
102
+ readonly span: PslSpan;
103
+ }
104
+
105
+ export interface FieldSymbol {
106
+ readonly kind: 'field';
107
+ readonly name: string;
108
+ readonly node: FieldDeclarationAst;
109
+ readonly span: PslSpan;
110
+ readonly typeName: string;
111
+ readonly typeNamespaceId?: string;
112
+ readonly typeContractSpaceId?: string;
113
+ readonly optional: boolean;
114
+ readonly list: boolean;
115
+ readonly typeConstructor?: ResolvedTypeConstructorCall;
116
+ readonly attributes: readonly ResolvedAttribute[];
117
+ /** Prevents cascading unsupported-type diagnostics after invalid qualification. */
118
+ readonly malformedType?: boolean;
119
+ }
120
+
121
+ export interface BuildSymbolTableOptions {
122
+ readonly document: DocumentAst;
123
+ readonly sourceFile: SourceFile;
124
+ readonly scalarTypes: readonly string[];
125
+ readonly pslBlockDescriptors: AuthoringPslBlockDescriptorNamespace;
126
+ }
127
+
128
+ export interface SymbolTableResult {
129
+ readonly table: SymbolTable;
130
+ readonly diagnostics: readonly ParseDiagnostic[];
131
+ }
132
+
133
+ /**
134
+ * Owns duplicate-declaration detection for all PSL scopes; downstream consumers
135
+ * should consume first-wins symbols rather than re-emitting duplicate diagnostics.
136
+ */
137
+ export function buildSymbolTable(options: BuildSymbolTableOptions): SymbolTableResult {
138
+ const { document, sourceFile, scalarTypes, pslBlockDescriptors } = options;
139
+ const diagnostics: ParseDiagnostic[] = [];
140
+ const scalarSet = new Set(scalarTypes);
141
+
142
+ const namespaces: Record<string, NamespaceSymbol> = {};
143
+ const scalars: Record<string, ScalarSymbol> = {};
144
+ const typeAliases: Record<string, TypeAliasSymbol> = {};
145
+ const blocks: Record<string, BlockSymbol> = {};
146
+ const models: Record<string, ModelSymbol> = {};
147
+ const compositeTypes: Record<string, CompositeTypeSymbol> = {};
148
+ const topLevelNames = new Set<string>();
149
+
150
+ const claim = (taken: Set<string>, name: IdentifierAst | undefined): string | undefined => {
151
+ const text = name?.name();
152
+ if (text === undefined) return undefined;
153
+ if (taken.has(text)) {
154
+ const range = nameRange(name, sourceFile);
155
+ if (range) {
156
+ diagnostics.push({
157
+ code: 'PSL_DUPLICATE_DECLARATION',
158
+ message: `Duplicate declaration of "${text}"`,
159
+ range,
160
+ });
161
+ }
162
+ return undefined;
163
+ }
164
+ taken.add(text);
165
+ return text;
166
+ };
167
+
168
+ for (const declaration of document.declarations()) {
169
+ if (declaration instanceof ModelDeclarationAst) {
170
+ const name = claim(topLevelNames, declaration.name());
171
+ if (name !== undefined) models[name] = buildModel(name, declaration, sourceFile, diagnostics);
172
+ } else if (declaration instanceof CompositeTypeDeclarationAst) {
173
+ const name = claim(topLevelNames, declaration.name());
174
+ if (name !== undefined) {
175
+ compositeTypes[name] = buildCompositeType(name, declaration, sourceFile, diagnostics);
176
+ }
177
+ } else if (declaration instanceof GenericBlockDeclarationAst) {
178
+ const name = claim(topLevelNames, declaration.name());
179
+ if (name !== undefined) {
180
+ blocks[name] = buildBlock(name, declaration, sourceFile, pslBlockDescriptors, diagnostics);
181
+ }
182
+ } else if (declaration instanceof NamespaceDeclarationAst) {
183
+ const name = claim(topLevelNames, declaration.name());
184
+ if (name !== undefined) {
185
+ namespaces[name] = buildNamespace(
186
+ name,
187
+ declaration,
188
+ diagnostics,
189
+ sourceFile,
190
+ pslBlockDescriptors,
191
+ );
192
+ }
193
+ } else if (declaration instanceof TypesBlockAst) {
194
+ for (const binding of declaration.declarations()) {
195
+ const name = claim(topLevelNames, binding.name());
196
+ if (name === undefined) continue;
197
+ const resolved = resolveNamedTypeBinding(binding, sourceFile);
198
+ const span = nodePslSpan(binding.syntax, sourceFile);
199
+ if (isScalarBinding(binding, scalarSet)) {
200
+ scalars[name] = { kind: 'scalar', name, node: binding, span, ...resolved };
201
+ } else {
202
+ typeAliases[name] = { kind: 'typeAlias', name, node: binding, span, ...resolved };
203
+ }
204
+ }
205
+ }
206
+ }
207
+
208
+ const table: SymbolTable = {
209
+ topLevel: { namespaces, scalars, typeAliases, blocks, models, compositeTypes },
210
+ };
211
+ return { table, diagnostics };
212
+ }
213
+
214
+ function buildModel(
215
+ name: string,
216
+ node: ModelDeclarationAst,
217
+ sourceFile: SourceFile,
218
+ diagnostics: ParseDiagnostic[],
219
+ ): ModelSymbol {
220
+ return {
221
+ kind: 'model',
222
+ name,
223
+ node,
224
+ span: nodePslSpan(node.syntax, sourceFile),
225
+ fields: buildFields(name, node.fields(), sourceFile, diagnostics),
226
+ attributes: readResolvedAttributes(node.attributes(), sourceFile),
227
+ };
228
+ }
229
+
230
+ function buildCompositeType(
231
+ name: string,
232
+ node: CompositeTypeDeclarationAst,
233
+ sourceFile: SourceFile,
234
+ diagnostics: ParseDiagnostic[],
235
+ ): CompositeTypeSymbol {
236
+ return {
237
+ kind: 'compositeType',
238
+ name,
239
+ node,
240
+ span: nodePslSpan(node.syntax, sourceFile),
241
+ fields: buildFields(name, node.fields(), sourceFile, diagnostics),
242
+ attributes: readResolvedAttributes(node.attributes(), sourceFile),
243
+ };
244
+ }
245
+
246
+ function buildBlock(
247
+ name: string,
248
+ node: GenericBlockDeclarationAst,
249
+ sourceFile: SourceFile,
250
+ pslBlockDescriptors: AuthoringPslBlockDescriptorNamespace,
251
+ diagnostics: ParseDiagnostic[],
252
+ ): BlockSymbol {
253
+ const keyword = node.keyword()?.text ?? '';
254
+ const descriptor = findBlockDescriptor(pslBlockDescriptors, keyword);
255
+ return {
256
+ kind: 'block',
257
+ name,
258
+ keyword,
259
+ node,
260
+ span: nodePslSpan(node.syntax, sourceFile),
261
+ block: reconstructExtensionBlock(node, descriptor, sourceFile, diagnostics),
262
+ };
263
+ }
264
+
265
+ function buildNamespace(
266
+ name: string,
267
+ node: NamespaceDeclarationAst,
268
+ diagnostics: ParseDiagnostic[],
269
+ sourceFile: SourceFile,
270
+ pslBlockDescriptors: AuthoringPslBlockDescriptorNamespace,
271
+ ): NamespaceSymbol {
272
+ const models: Record<string, ModelSymbol> = {};
273
+ const compositeTypes: Record<string, CompositeTypeSymbol> = {};
274
+ const blocks: Record<string, BlockSymbol> = {};
275
+ const taken = new Set<string>();
276
+
277
+ for (const member of node.declarations()) {
278
+ const memberName = member.name()?.name();
279
+ if (memberName === undefined) continue;
280
+ if (taken.has(memberName)) {
281
+ const range = nameRange(member.name(), sourceFile);
282
+ if (range) {
283
+ diagnostics.push({
284
+ code: 'PSL_DUPLICATE_DECLARATION',
285
+ message: `Duplicate declaration of "${memberName}"`,
286
+ range,
287
+ });
288
+ }
289
+ continue;
290
+ }
291
+ taken.add(memberName);
292
+ if (member instanceof ModelDeclarationAst) {
293
+ models[memberName] = buildModel(memberName, member, sourceFile, diagnostics);
294
+ } else if (member instanceof CompositeTypeDeclarationAst) {
295
+ compositeTypes[memberName] = buildCompositeType(memberName, member, sourceFile, diagnostics);
296
+ } else if (member instanceof GenericBlockDeclarationAst) {
297
+ blocks[memberName] = buildBlock(
298
+ memberName,
299
+ member,
300
+ sourceFile,
301
+ pslBlockDescriptors,
302
+ diagnostics,
303
+ );
304
+ }
305
+ }
306
+
307
+ return {
308
+ kind: 'namespace',
309
+ name,
310
+ node,
311
+ span: nodePslSpan(node.syntax, sourceFile),
312
+ models,
313
+ compositeTypes,
314
+ blocks,
315
+ };
316
+ }
317
+
318
+ function buildFields(
319
+ ownerName: string,
320
+ fields: Iterable<FieldDeclarationAst>,
321
+ sourceFile: SourceFile,
322
+ diagnostics: ParseDiagnostic[],
323
+ ): Record<string, FieldSymbol> {
324
+ const result: Record<string, FieldSymbol> = {};
325
+ for (const field of fields) {
326
+ const nameNode = field.name();
327
+ const name = nameNode?.name();
328
+ if (name === undefined) continue;
329
+ if (Object.hasOwn(result, name)) {
330
+ const range = nameRange(nameNode, sourceFile);
331
+ if (range) {
332
+ diagnostics.push({
333
+ code: 'PSL_DUPLICATE_DECLARATION',
334
+ message: `Duplicate declaration of "${name}"`,
335
+ range,
336
+ });
337
+ }
338
+ continue;
339
+ }
340
+ result[name] = buildField(ownerName, name, field, sourceFile, diagnostics);
341
+ }
342
+ return result;
343
+ }
344
+
345
+ function buildField(
346
+ ownerName: string,
347
+ name: string,
348
+ node: FieldDeclarationAst,
349
+ sourceFile: SourceFile,
350
+ diagnostics: ParseDiagnostic[],
351
+ ): FieldSymbol {
352
+ const attributes = readResolvedAttributes(node.attributes(), sourceFile);
353
+ const span = nodePslSpan(node.syntax, sourceFile);
354
+ const annotation = node.typeAnnotation();
355
+ const typeName = annotation?.name();
356
+
357
+ if (typeName?.isOverQualified()) {
358
+ const path = typeName.path();
359
+ diagnostics.push({
360
+ code: 'PSL_INVALID_QUALIFIED_TYPE',
361
+ message: `Field "${ownerName}.${name}" has an invalid qualified type "${path.join('.')}"; use at most one namespace qualifier (e.g. "ns.TypeName")`,
362
+ range: nodeRange(typeName.syntax, sourceFile),
363
+ });
364
+ return {
365
+ kind: 'field',
366
+ name,
367
+ node,
368
+ span,
369
+ typeName: path[path.length - 1] ?? '',
370
+ optional: false,
371
+ list: false,
372
+ malformedType: true,
373
+ attributes,
374
+ };
375
+ }
376
+
377
+ const typeConstructor = annotation?.isConstructor()
378
+ ? readResolvedConstructorCall(annotation, sourceFile)
379
+ : undefined;
380
+ const typeNamespaceId = typeName?.namespace()?.name();
381
+ const typeContractSpaceId = typeName?.space()?.name();
382
+
383
+ return {
384
+ kind: 'field',
385
+ name,
386
+ node,
387
+ span,
388
+ typeName: typeName?.identifier()?.name() ?? '',
389
+ ...(typeNamespaceId !== undefined ? { typeNamespaceId } : {}),
390
+ ...(typeContractSpaceId !== undefined ? { typeContractSpaceId } : {}),
391
+ optional: annotation?.isOptional() ?? false,
392
+ list: annotation?.isList() ?? false,
393
+ ...(typeConstructor !== undefined ? { typeConstructor } : {}),
394
+ attributes,
395
+ };
396
+ }
397
+
398
+ function resolveNamedTypeBinding(
399
+ node: NamedTypeDeclarationAst,
400
+ sourceFile: SourceFile,
401
+ ): {
402
+ baseType?: string;
403
+ typeConstructor?: ResolvedTypeConstructorCall;
404
+ isConstructor: boolean;
405
+ attributes: readonly ResolvedAttribute[];
406
+ } {
407
+ const annotation = node.typeAnnotation();
408
+ const isConstructor = annotation?.isConstructor() ?? false;
409
+ const baseType = annotation?.name()?.identifier()?.name();
410
+ const typeConstructor = readResolvedConstructorCall(annotation, sourceFile);
411
+ return {
412
+ isConstructor,
413
+ ...(!isConstructor && baseType !== undefined ? { baseType } : {}),
414
+ ...(typeConstructor !== undefined ? { typeConstructor } : {}),
415
+ attributes: readResolvedAttributes(node.attributes(), sourceFile),
416
+ };
417
+ }
418
+
419
+ function isScalarBinding(node: NamedTypeDeclarationAst, scalarTypes: Set<string>): boolean {
420
+ const annotation = node.typeAnnotation();
421
+ if (annotation === undefined || annotation.isConstructor()) return false;
422
+ const base = annotation.name()?.identifier()?.name();
423
+ return base !== undefined && scalarTypes.has(base);
424
+ }
425
+
426
+ function nameRange(name: IdentifierAst | undefined, sourceFile: SourceFile): Range | undefined {
427
+ if (name === undefined) return undefined;
428
+ for (const token of name.syntax.tokens()) {
429
+ if (token.kind === 'Ident') {
430
+ return {
431
+ start: sourceFile.positionAt(token.offset),
432
+ end: sourceFile.positionAt(token.offset + token.text.length),
433
+ };
434
+ }
435
+ }
436
+ return undefined;
437
+ }
438
+
439
+ function nodeRange(node: SyntaxNode, sourceFile: SourceFile): Range {
440
+ const start = node.offset;
441
+ const end = start + node.green.textLength;
442
+ return {
443
+ start: sourceFile.positionAt(start),
444
+ end: sourceFile.positionAt(end),
445
+ };
446
+ }
@@ -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(): Token | undefined {
14
+ lparen(): SyntaxToken | undefined {
16
15
  return findChildToken(this.syntax, 'LParen');
17
16
  }
18
17
 
19
- rparen(): Token | undefined {
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(): Token | undefined {
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(): Token | undefined {
62
+ doubleAt(): SyntaxToken | undefined {
64
63
  return findChildToken(this.syntax, 'DoubleAt');
65
64
  }
66
65