@prisma-next/psl-parser 0.3.0-dev.99 → 0.4.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 CHANGED
@@ -6,7 +6,7 @@ Reusable PSL parser for Prisma Next.
6
6
 
7
7
  `@prisma-next/psl-parser` parses Prisma Schema Language (PSL) source into a deterministic AST with source spans and stable machine-readable diagnostics. It is intentionally parser-only: normalization to contract IR and emit integration happen in downstream milestones/packages.
8
8
 
9
- In the provider-based authoring model, PSL providers call this parser and then return `Result<ContractIR, ContractSourceDiagnostics>` to the framework emit pipeline.
9
+ In the provider-based authoring model, PSL providers call this parser and then return `Result<Contract, ContractSourceDiagnostics>` to the framework emit pipeline.
10
10
 
11
11
  ## Responsibilities
12
12
 
@@ -22,7 +22,7 @@ In the provider-based authoring model, PSL providers call this parser and then r
22
22
 
23
23
  `@prisma-next/psl-parser` parses attributes **generically**:
24
24
 
25
- - Attributes may be **non-namespaced** (for example `@id`) or **namespaced** (for example `@pgvector.column`).
25
+ - Attributes may be **non-namespaced** (for example `@id`) or **namespaced** (for example `@vendor.option`).
26
26
  - Attributes may include an **optional argument list**.
27
27
  - Arguments are parsed into positional/named entries with preserved raw values and source spans.
28
28
  - The parser owns **syntax + structure + spans**, not semantics.
@@ -73,4 +73,3 @@ flowchart LR
73
73
  - `docs/Architecture Overview.md`
74
74
  - `docs/architecture docs/subsystems/2. Contract Emitter & Types.md`
75
75
  - `docs/architecture docs/adrs/ADR 163 - Provider-invoked source interpretation packages.md`
76
-
package/dist/index.d.mts CHANGED
@@ -1,3 +1,9 @@
1
- import { S as PslPosition, T as PslTypesBlock, _ as PslFieldAttribute, a as PslAttributeNamedArgument, b as PslModelAttribute, c as PslDefaultFunctionValue, d as PslDiagnostic, f as PslDiagnosticCode, g as PslField, h as PslEnumValue, i as PslAttributeArgument, l as PslDefaultLiteralValue, m as PslEnum, n as ParsePslDocumentResult, o as PslAttributePositionalArgument, p as PslDocumentAst, r as PslAttribute, s as PslAttributeTarget, t as ParsePslDocumentInput, u as PslDefaultValue, w as PslSpan, x as PslNamedTypeDeclaration, y as PslModel } from "./types-CyfspkvF.mjs";
2
- import { t as parsePslDocument } from "./parser-BgdIJNqs.mjs";
3
- export { type ParsePslDocumentInput, type ParsePslDocumentResult, type PslAttribute, type PslAttributeArgument, type PslAttributeNamedArgument, type PslAttributePositionalArgument, type PslAttributeTarget, type PslDefaultFunctionValue, type PslDefaultLiteralValue, type PslDefaultValue, type PslDiagnostic, type PslDiagnosticCode, type PslDocumentAst, type PslEnum, type PslEnumValue, type PslField, type PslFieldAttribute, type PslModel, type PslModelAttribute, type PslNamedTypeDeclaration, type PslPosition, type PslSpan, type PslTypesBlock, parsePslDocument };
1
+ import { C as PslPosition, D as PslTypesBlock, E as PslTypeConstructorCall, S as PslNamedTypeDeclaration, T as PslSpan, _ as PslField, a as PslAttributeNamedArgument, b as PslModel, c as PslCompositeType, d as PslDefaultValue, f as PslDiagnostic, g as PslEnumValue, h as PslEnum, i as PslAttributeArgument, l as PslDefaultFunctionValue, m as PslDocumentAst, n as ParsePslDocumentResult, o as PslAttributePositionalArgument, p as PslDiagnosticCode, r as PslAttribute, s as PslAttributeTarget, t as ParsePslDocumentInput, u as PslDefaultLiteralValue, v as PslFieldAttribute, x as PslModelAttribute } from "./types-CYb3hCxS.mjs";
2
+ import { t as parsePslDocument } from "./parser-B_U8b1dE.mjs";
3
+
4
+ //#region src/attribute-helpers.d.ts
5
+ declare function getPositionalArgument(attribute: PslAttribute, index?: number): string | undefined;
6
+ declare function parseQuotedStringLiteral(value: string): string | undefined;
7
+ //#endregion
8
+ export { type ParsePslDocumentInput, type ParsePslDocumentResult, type PslAttribute, type PslAttributeArgument, type PslAttributeNamedArgument, type PslAttributePositionalArgument, type PslAttributeTarget, type PslCompositeType, type PslDefaultFunctionValue, type PslDefaultLiteralValue, type PslDefaultValue, type PslDiagnostic, type PslDiagnosticCode, type PslDocumentAst, type PslEnum, type PslEnumValue, type PslField, type PslFieldAttribute, type PslModel, type PslModelAttribute, type PslNamedTypeDeclaration, type PslPosition, type PslSpan, type PslTypeConstructorCall, type PslTypesBlock, getPositionalArgument, parsePslDocument, parseQuotedStringLiteral };
9
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/attribute-helpers.ts"],"sourcesContent":[],"mappings":";;;;iBAEgB,qBAAA,YAAiC;iBAKjC,wBAAA"}
package/dist/index.mjs CHANGED
@@ -1,3 +1,15 @@
1
- import { t as parsePslDocument } from "./parser-Br4vqzd-.mjs";
1
+ import { t as parsePslDocument } from "./parser-Cr64fw1E.mjs";
2
2
 
3
- export { parsePslDocument };
3
+ //#region src/attribute-helpers.ts
4
+ function getPositionalArgument(attribute, index = 0) {
5
+ return attribute.args.filter((arg) => arg.kind === "positional")[index]?.value;
6
+ }
7
+ function parseQuotedStringLiteral(value) {
8
+ const match = value.trim().match(/^(['"])(.*)\1$/);
9
+ if (!match) return void 0;
10
+ return match[2] ?? "";
11
+ }
12
+
13
+ //#endregion
14
+ export { getPositionalArgument, parsePslDocument, parseQuotedStringLiteral };
15
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/attribute-helpers.ts"],"sourcesContent":["import type { PslAttribute } from './types';\n\nexport function getPositionalArgument(attribute: PslAttribute, index = 0): string | undefined {\n const entries = attribute.args.filter((arg) => arg.kind === 'positional');\n return entries[index]?.value;\n}\n\nexport function parseQuotedStringLiteral(value: string): string | undefined {\n const trimmed = value.trim();\n const match = trimmed.match(/^(['\"])(.*)\\1$/);\n if (!match) return undefined;\n return match[2] ?? '';\n}\n"],"mappings":";;;AAEA,SAAgB,sBAAsB,WAAyB,QAAQ,GAAuB;AAE5F,QADgB,UAAU,KAAK,QAAQ,QAAQ,IAAI,SAAS,aAAa,CAC1D,QAAQ;;AAGzB,SAAgB,yBAAyB,OAAmC;CAE1E,MAAM,QADU,MAAM,MAAM,CACN,MAAM,iBAAiB;AAC7C,KAAI,CAAC,MAAO,QAAO;AACnB,QAAO,MAAM,MAAM"}
@@ -1,7 +1,7 @@
1
- import { n as ParsePslDocumentResult, t as ParsePslDocumentInput } from "./types-CyfspkvF.mjs";
1
+ import { n as ParsePslDocumentResult, t as ParsePslDocumentInput } from "./types-CYb3hCxS.mjs";
2
2
 
3
3
  //#region src/parser.d.ts
4
4
  declare function parsePslDocument(input: ParsePslDocumentInput): ParsePslDocumentResult;
5
5
  //#endregion
6
6
  export { parsePslDocument as t };
7
- //# sourceMappingURL=parser-BgdIJNqs.d.mts.map
7
+ //# sourceMappingURL=parser-B_U8b1dE.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser-B_U8b1dE.d.mts","names":[],"sources":["../src/parser.ts"],"sourcesContent":[],"mappings":";;;iBAkDgB,gBAAA,QAAwB,wBAAwB"}
@@ -26,6 +26,7 @@ function parsePslDocument(input) {
26
26
  };
27
27
  const models = [];
28
28
  const enums = [];
29
+ const compositeTypes = [];
29
30
  let typesBlock;
30
31
  let lineIndex = 0;
31
32
  while (lineIndex < lines.length) {
@@ -58,6 +59,18 @@ function parsePslDocument(input) {
58
59
  lineIndex = bounds.endLine + 1;
59
60
  continue;
60
61
  }
62
+ const compositeTypeMatch = line.match(/^type\s+([A-Za-z_]\w*)\s*\{$/);
63
+ if (compositeTypeMatch) {
64
+ const bounds = findBlockBounds(context, lineIndex);
65
+ const name = compositeTypeMatch[1] ?? "";
66
+ if (name.length === 0) {
67
+ lineIndex = bounds.endLine + 1;
68
+ continue;
69
+ }
70
+ compositeTypes.push(parseCompositeTypeBlock(context, name, bounds));
71
+ lineIndex = bounds.endLine + 1;
72
+ continue;
73
+ }
61
74
  if (/^types\s*\{$/.test(line)) {
62
75
  const bounds = findBlockBounds(context, lineIndex);
63
76
  typesBlock = parseTypesBlock(context, bounds);
@@ -83,6 +96,7 @@ function parsePslDocument(input) {
83
96
  const namedTypeNames = new Set((typesBlock?.declarations ?? []).map((declaration) => declaration.name));
84
97
  const modelNames = new Set(models.map((model) => model.name));
85
98
  const enumNames = new Set(enums.map((enumBlock) => enumBlock.name));
99
+ const compositeTypeNames = new Set(compositeTypes.map((ct) => ct.name));
86
100
  for (const declaration of typesBlock?.declarations ?? []) {
87
101
  if (SCALAR_TYPES.has(declaration.name)) {
88
102
  pushDiagnostic(context, {
@@ -110,7 +124,7 @@ function parsePslDocument(input) {
110
124
  ...model,
111
125
  fields: model.fields.map((field) => {
112
126
  if (!namedTypeNames.has(field.typeName)) return field;
113
- if (field.attributes.some((attribute) => attribute.name === "relation") || modelNames.has(field.typeName) || enumNames.has(field.typeName) || SCALAR_TYPES.has(field.typeName)) return field;
127
+ if (field.attributes.some((attribute) => attribute.name === "relation") || modelNames.has(field.typeName) || enumNames.has(field.typeName) || compositeTypeNames.has(field.typeName) || SCALAR_TYPES.has(field.typeName)) return field;
114
128
  return {
115
129
  ...field,
116
130
  typeRef: field.typeName
@@ -123,6 +137,7 @@ function parsePslDocument(input) {
123
137
  sourceId: input.sourceId,
124
138
  models: normalizedModels,
125
139
  enums,
140
+ compositeTypes,
126
141
  ...ifDefined("types", typesBlock),
127
142
  span: {
128
143
  start: createPosition(context, 0, 0),
@@ -155,11 +170,39 @@ function parseModelBlock(context, name, bounds) {
155
170
  span: createLineRangeSpan(context, bounds.startLine, bounds.endLine)
156
171
  };
157
172
  }
173
+ function parseCompositeTypeBlock(context, name, bounds) {
174
+ const fields = [];
175
+ const attributes = [];
176
+ for (let lineIndex = bounds.startLine + 1; lineIndex < bounds.endLine; lineIndex += 1) {
177
+ const line = stripInlineComment(context.lines[lineIndex] ?? "").trim();
178
+ if (line.length === 0) continue;
179
+ if (line.startsWith("@@")) {
180
+ const attribute = parseModelAttribute(context, line, lineIndex);
181
+ if (attribute) attributes.push(attribute);
182
+ continue;
183
+ }
184
+ const field = parseField(context, line, lineIndex);
185
+ if (field) fields.push(field);
186
+ }
187
+ return {
188
+ kind: "compositeType",
189
+ name,
190
+ fields,
191
+ attributes,
192
+ span: createLineRangeSpan(context, bounds.startLine, bounds.endLine)
193
+ };
194
+ }
158
195
  function parseEnumBlock(context, name, bounds) {
159
196
  const values = [];
197
+ const attributes = [];
160
198
  for (let lineIndex = bounds.startLine + 1; lineIndex < bounds.endLine; lineIndex += 1) {
161
199
  const line = stripInlineComment(context.lines[lineIndex] ?? "").trim();
162
200
  if (line.length === 0) continue;
201
+ if (line.startsWith("@@")) {
202
+ const attribute = parseEnumAttribute(context, line, lineIndex);
203
+ if (attribute) attributes.push(attribute);
204
+ continue;
205
+ }
163
206
  const valueMatch = line.match(/^([A-Za-z_]\w*)$/);
164
207
  if (!valueMatch) {
165
208
  pushDiagnostic(context, {
@@ -179,6 +222,7 @@ function parseEnumBlock(context, name, bounds) {
179
222
  kind: "enum",
180
223
  name,
181
224
  values,
225
+ attributes,
182
226
  span: createLineRangeSpan(context, bounds.startLine, bounds.endLine)
183
227
  };
184
228
  }
@@ -188,7 +232,7 @@ function parseTypesBlock(context, bounds) {
188
232
  const raw = context.lines[lineIndex] ?? "";
189
233
  const line = stripInlineComment(raw).trim();
190
234
  if (line.length === 0) continue;
191
- const declarationMatch = line.match(/^([A-Za-z_]\w*)\s*=\s*([A-Za-z_]\w*)(.*)$/);
235
+ const declarationMatch = line.match(/^([A-Za-z_]\w*)\s*=\s*(.+)$/);
192
236
  if (!declarationMatch) {
193
237
  pushDiagnostic(context, {
194
238
  code: "PSL_INVALID_TYPES_MEMBER",
@@ -198,13 +242,23 @@ function parseTypesBlock(context, bounds) {
198
242
  continue;
199
243
  }
200
244
  const declarationName = declarationMatch[1] ?? "";
201
- const baseType = declarationMatch[2] ?? "";
202
- const attributePart = declarationMatch[3] ?? "";
203
245
  const trimmedStartColumn = firstNonWhitespaceColumn(raw);
204
- const attributeOffset = line.length - attributePart.length;
205
- const attributeSource = attributePart.trimStart();
206
- const leadingAttributeWhitespace = attributePart.length - attributeSource.length;
207
- const attributeParse = extractAttributeTokensWithSpans(context, lineIndex, attributeSource, trimmedStartColumn + attributeOffset + leadingAttributeWhitespace);
246
+ const declarationValue = (declarationMatch[2] ?? "").trim();
247
+ const valueOffset = line.indexOf(declarationValue);
248
+ const declarationValueColumn = trimmedStartColumn + Math.max(valueOffset, 0);
249
+ const typeAndAttributeSplit = splitTypeAndAttributes(declarationValue);
250
+ const typeSource = typeAndAttributeSplit.typeSource.trim();
251
+ const attributeSource = typeAndAttributeSplit.attributeSource.trimStart();
252
+ const leadingAttributeWhitespace = typeAndAttributeSplit.attributeSource.length - attributeSource.length;
253
+ const typeConstructor = parseTypeConstructorCall(context, {
254
+ declarationValue: typeSource,
255
+ lineIndex,
256
+ startColumn: declarationValueColumn,
257
+ invalidCode: "PSL_INVALID_TYPES_MEMBER",
258
+ invalidMessage: (value) => `Invalid types declaration "${value}"`
259
+ });
260
+ if (typeConstructor === "malformed") continue;
261
+ const attributeParse = extractAttributeTokensWithSpans(context, lineIndex, attributeSource, declarationValueColumn + typeAndAttributeSplit.attributeOffset + leadingAttributeWhitespace);
208
262
  if (!attributeParse.ok) continue;
209
263
  const attributes = attributeParse.tokens.map((token) => parseAttributeToken(context, {
210
264
  token: token.text,
@@ -212,6 +266,26 @@ function parseTypesBlock(context, bounds) {
212
266
  lineIndex,
213
267
  span: token.span
214
268
  })).filter((attribute) => Boolean(attribute));
269
+ if (typeConstructor) {
270
+ declarations.push({
271
+ kind: "namedType",
272
+ name: declarationName,
273
+ typeConstructor,
274
+ attributes,
275
+ span: createTrimmedLineSpan(context, lineIndex)
276
+ });
277
+ continue;
278
+ }
279
+ const baseTypeMatch = typeSource.match(/^([A-Za-z_]\w*)$/);
280
+ if (!baseTypeMatch) {
281
+ pushDiagnostic(context, {
282
+ code: "PSL_INVALID_TYPES_MEMBER",
283
+ message: `Invalid types declaration "${line}"`,
284
+ span: createTrimmedLineSpan(context, lineIndex)
285
+ });
286
+ continue;
287
+ }
288
+ const baseType = baseTypeMatch[1] ?? "";
215
289
  declarations.push({
216
290
  kind: "namedType",
217
291
  name: declarationName,
@@ -226,6 +300,39 @@ function parseTypesBlock(context, bounds) {
226
300
  span: createLineRangeSpan(context, bounds.startLine, bounds.endLine)
227
301
  };
228
302
  }
303
+ function parseTypeConstructorCall(context, input) {
304
+ const value = input.declarationValue.trim();
305
+ const constructorMatch = value.match(/^([A-Za-z_][A-Za-z0-9_-]*(?:\.[A-Za-z_][A-Za-z0-9_-]*)*)\s*\(/);
306
+ if (!constructorMatch) return;
307
+ const openParen = value.indexOf("(");
308
+ const closeParen = value.lastIndexOf(")");
309
+ if (closeParen !== value.length - 1) {
310
+ pushDiagnostic(context, {
311
+ code: input.invalidCode,
312
+ message: input.invalidMessage(value),
313
+ span: createInlineSpan(context, input.lineIndex, input.startColumn, input.startColumn + value.length)
314
+ });
315
+ return "malformed";
316
+ }
317
+ const constructorPath = constructorMatch[1] ?? "";
318
+ const args = parseArgumentList(context, {
319
+ argsRaw: value.slice(openParen + 1, closeParen),
320
+ argsOffset: input.startColumn + openParen + 1,
321
+ lineIndex: input.lineIndex,
322
+ token: value,
323
+ span: createInlineSpan(context, input.lineIndex, input.startColumn, input.startColumn + value.length),
324
+ invalidCode: input.invalidCode,
325
+ invalidEmptyArgumentMessage: `Invalid empty argument in type constructor "${value}"`,
326
+ invalidNamedArgumentMessage: (part) => `Invalid named argument syntax "${part}" in type constructor "${value}"`
327
+ });
328
+ if (!args) return "malformed";
329
+ return {
330
+ kind: "typeConstructor",
331
+ path: constructorPath.split("."),
332
+ args,
333
+ span: createInlineSpan(context, input.lineIndex, input.startColumn, input.startColumn + value.length)
334
+ };
335
+ }
229
336
  function parseModelAttribute(context, line, lineIndex) {
230
337
  const tokenParse = extractAttributeTokensWithSpans(context, lineIndex, line, firstNonWhitespaceColumn(context.lines[lineIndex] ?? ""));
231
338
  if (!tokenParse.ok || tokenParse.tokens.length !== 1) {
@@ -245,8 +352,37 @@ function parseModelAttribute(context, line, lineIndex) {
245
352
  span: token.span
246
353
  });
247
354
  }
355
+ function parseEnumAttribute(context, line, lineIndex) {
356
+ const tokenParse = extractAttributeTokensWithSpans(context, lineIndex, line, firstNonWhitespaceColumn(context.lines[lineIndex] ?? ""));
357
+ if (!tokenParse.ok || tokenParse.tokens.length !== 1) {
358
+ pushDiagnostic(context, {
359
+ code: "PSL_INVALID_ENUM_MEMBER",
360
+ message: `Invalid enum value declaration "${line}"`,
361
+ span: createTrimmedLineSpan(context, lineIndex)
362
+ });
363
+ return;
364
+ }
365
+ const token = tokenParse.tokens[0];
366
+ if (!token) return;
367
+ const parsed = parseAttributeToken(context, {
368
+ token: token.text,
369
+ target: "enum",
370
+ lineIndex,
371
+ span: token.span
372
+ });
373
+ if (!parsed) return;
374
+ if (parsed.name !== "map") {
375
+ pushDiagnostic(context, {
376
+ code: "PSL_INVALID_ENUM_MEMBER",
377
+ message: `Invalid enum value declaration "${line}"`,
378
+ span: createTrimmedLineSpan(context, lineIndex)
379
+ });
380
+ return;
381
+ }
382
+ return parsed;
383
+ }
248
384
  function parseField(context, line, lineIndex) {
249
- const fieldMatch = line.match(/^([A-Za-z_]\w*)\s+([A-Za-z_]\w*(?:\[\])?)(\?)?(.*)$/);
385
+ const fieldMatch = line.match(/^([A-Za-z_]\w*)(\s+)(.+)$/);
250
386
  if (!fieldMatch) {
251
387
  pushDiagnostic(context, {
252
388
  code: "PSL_INVALID_MODEL_MEMBER",
@@ -256,22 +392,42 @@ function parseField(context, line, lineIndex) {
256
392
  return;
257
393
  }
258
394
  const fieldName = fieldMatch[1] ?? "";
259
- const rawTypeToken = fieldMatch[2] ?? "";
260
- const optionalMarker = fieldMatch[3] ?? "";
261
- const attributePart = fieldMatch[4] ?? "";
262
- const list = rawTypeToken.endsWith("[]");
263
- const typeName = list ? rawTypeToken.slice(0, -2) : rawTypeToken;
264
- const optional = optionalMarker === "?";
265
- const attributes = [];
395
+ const separator = fieldMatch[2] ?? "";
396
+ const typeAndAttributeSplit = splitTypeAndAttributes(fieldMatch[3] ?? "");
397
+ const rawTypeSource = typeAndAttributeSplit.typeSource.trim();
398
+ const attributePart = typeAndAttributeSplit.attributeSource;
399
+ const optional = rawTypeSource.endsWith("?");
400
+ const typeSourceWithoutOptional = optional ? rawTypeSource.slice(0, -1).trimEnd() : rawTypeSource;
401
+ const list = typeSourceWithoutOptional.endsWith("[]");
402
+ const baseTypeSource = list ? typeSourceWithoutOptional.slice(0, -2).trimEnd() : typeSourceWithoutOptional;
266
403
  const trimmedStartColumn = firstNonWhitespaceColumn(context.lines[lineIndex] ?? "");
267
- const attributeOffset = line.length - attributePart.length;
404
+ const typeConstructor = parseTypeConstructorCall(context, {
405
+ declarationValue: baseTypeSource,
406
+ lineIndex,
407
+ startColumn: trimmedStartColumn + fieldName.length + separator.length,
408
+ invalidCode: "PSL_INVALID_MODEL_MEMBER",
409
+ invalidMessage: (value) => `Invalid field type constructor "${value}"`
410
+ });
411
+ if (typeConstructor === "malformed") return;
412
+ const simpleTypeMatch = baseTypeSource.match(/^([A-Za-z_]\w*)$/);
413
+ const typeName = typeConstructor?.path.join(".") ?? simpleTypeMatch?.[1];
414
+ if (!typeName) {
415
+ pushDiagnostic(context, {
416
+ code: "PSL_INVALID_MODEL_MEMBER",
417
+ message: `Invalid model member declaration "${line}"`,
418
+ span: createTrimmedLineSpan(context, lineIndex)
419
+ });
420
+ return;
421
+ }
422
+ const attributes = [];
268
423
  const attributeSource = attributePart.trimStart();
269
424
  const leadingAttributeWhitespace = attributePart.length - attributeSource.length;
270
- const tokenParse = extractAttributeTokensWithSpans(context, lineIndex, attributeSource, trimmedStartColumn + attributeOffset + leadingAttributeWhitespace);
425
+ const tokenParse = extractAttributeTokensWithSpans(context, lineIndex, attributeSource, trimmedStartColumn + fieldName.length + separator.length + typeAndAttributeSplit.attributeOffset + leadingAttributeWhitespace);
271
426
  if (!tokenParse.ok) return {
272
427
  kind: "field",
273
428
  name: fieldName,
274
429
  typeName,
430
+ ...ifDefined("typeConstructor", typeConstructor),
275
431
  optional,
276
432
  list,
277
433
  attributes,
@@ -290,23 +446,81 @@ function parseField(context, line, lineIndex) {
290
446
  kind: "field",
291
447
  name: fieldName,
292
448
  typeName,
449
+ ...ifDefined("typeConstructor", typeConstructor),
293
450
  optional,
294
451
  list,
295
452
  attributes,
296
453
  span: createTrimmedLineSpan(context, lineIndex)
297
454
  };
298
455
  }
456
+ function isQuoteEscaped(value, quoteIndex) {
457
+ let backslashCount = 0;
458
+ for (let index = quoteIndex - 1; index >= 0 && value[index] === "\\"; index -= 1) backslashCount += 1;
459
+ return backslashCount % 2 === 1;
460
+ }
461
+ function splitTypeAndAttributes(value) {
462
+ let depthParen = 0;
463
+ let depthBracket = 0;
464
+ let depthBrace = 0;
465
+ let quote = null;
466
+ for (let index = 0; index < value.length; index += 1) {
467
+ const character = value[index] ?? "";
468
+ if (quote) {
469
+ if (character === quote && !isQuoteEscaped(value, index)) quote = null;
470
+ continue;
471
+ }
472
+ if (character === "\"" || character === "'") {
473
+ quote = character;
474
+ continue;
475
+ }
476
+ if (character === "(") {
477
+ depthParen += 1;
478
+ continue;
479
+ }
480
+ if (character === ")") {
481
+ depthParen = Math.max(0, depthParen - 1);
482
+ continue;
483
+ }
484
+ if (character === "[") {
485
+ depthBracket += 1;
486
+ continue;
487
+ }
488
+ if (character === "]") {
489
+ depthBracket = Math.max(0, depthBracket - 1);
490
+ continue;
491
+ }
492
+ if (character === "{") {
493
+ depthBrace += 1;
494
+ continue;
495
+ }
496
+ if (character === "}") {
497
+ depthBrace = Math.max(0, depthBrace - 1);
498
+ continue;
499
+ }
500
+ if (character === "@" && depthParen === 0 && depthBracket === 0 && depthBrace === 0) return {
501
+ typeSource: value.slice(0, index).trimEnd(),
502
+ attributeSource: value.slice(index),
503
+ attributeOffset: index
504
+ };
505
+ }
506
+ return {
507
+ typeSource: value.trimEnd(),
508
+ attributeSource: "",
509
+ attributeOffset: value.length
510
+ };
511
+ }
299
512
  function parseAttributeToken(context, input) {
300
- const expectsModelPrefix = input.target === "model";
301
- if (expectsModelPrefix && !input.token.startsWith("@@")) {
513
+ const expectsBlockPrefix = input.target === "model" || input.target === "enum";
514
+ const targetLabel = input.target === "enum" ? "Enum" : "Model";
515
+ if (expectsBlockPrefix && !input.token.startsWith("@@")) {
302
516
  pushDiagnostic(context, {
303
517
  code: "PSL_INVALID_ATTRIBUTE_SYNTAX",
304
- message: `Model attribute "${input.token}" must use @@ prefix`,
518
+ message: `${targetLabel} attribute "${input.token}" must use @@ prefix`,
305
519
  span: input.span
306
520
  });
307
521
  return;
308
522
  }
309
- if (!expectsModelPrefix && !input.token.startsWith("@")) {
523
+ if (!expectsBlockPrefix && !input.token.startsWith("@")) {
310
524
  pushDiagnostic(context, {
311
525
  code: "PSL_INVALID_ATTRIBUTE_SYNTAX",
312
526
  message: `Attribute "${input.token}" must use @ prefix`,
@@ -314,7 +528,7 @@ function parseAttributeToken(context, input) {
314
528
  });
315
529
  return;
316
530
  }
317
- if (!expectsModelPrefix && input.token.startsWith("@@")) {
531
+ if (!expectsBlockPrefix && input.token.startsWith("@@")) {
318
532
  pushDiagnostic(context, {
319
533
  code: "PSL_INVALID_ATTRIBUTE_SYNTAX",
320
534
  message: `Attribute "${input.token}" is not valid in ${input.target} context`,
@@ -322,7 +536,7 @@ function parseAttributeToken(context, input) {
322
536
  });
323
537
  return;
324
538
  }
325
- const rawBody = expectsModelPrefix ? input.token.slice(2) : input.token.slice(1);
539
+ const rawBody = expectsBlockPrefix ? input.token.slice(2) : input.token.slice(1);
326
540
  const openParen = rawBody.indexOf("(");
327
541
  const closeParen = rawBody.lastIndexOf(")");
328
542
  const hasArgs = openParen >= 0 || closeParen >= 0;
@@ -353,12 +567,15 @@ function parseAttributeToken(context, input) {
353
567
  });
354
568
  return;
355
569
  }
356
- const parsedArgs = parseAttributeArguments(context, {
570
+ const parsedArgs = parseArgumentList(context, {
357
571
  argsRaw: rawBody.slice(openParen + 1, closeParen),
358
- argsOffset: input.span.start.column - 1 + (expectsModelPrefix ? 2 : 1) + openParen + 1,
572
+ argsOffset: input.span.start.column - 1 + (expectsBlockPrefix ? 2 : 1) + openParen + 1,
359
573
  lineIndex: input.lineIndex,
360
574
  token: input.token,
361
- span: input.span
575
+ span: input.span,
576
+ invalidCode: "PSL_INVALID_ATTRIBUTE_SYNTAX",
577
+ invalidEmptyArgumentMessage: `Invalid empty argument in attribute "${input.token}"`,
578
+ invalidNamedArgumentMessage: (part) => `Invalid named argument syntax "${part}"`
362
579
  });
363
580
  if (!parsedArgs) return;
364
581
  args = parsedArgs;
@@ -371,7 +588,7 @@ function parseAttributeToken(context, input) {
371
588
  span: input.span
372
589
  };
373
590
  }
374
- function parseAttributeArguments(context, input) {
591
+ function parseArgumentList(context, input) {
375
592
  if (input.argsRaw.trim().length === 0) return [];
376
593
  const parts = splitTopLevelSegments(input.argsRaw, ",");
377
594
  const args = [];
@@ -380,8 +597,8 @@ function parseAttributeArguments(context, input) {
380
597
  const trimmedPart = original.trim();
381
598
  if (trimmedPart.length === 0) {
382
599
  pushDiagnostic(context, {
383
- code: "PSL_INVALID_ATTRIBUTE_SYNTAX",
384
- message: `Invalid empty argument in attribute "${input.token}"`,
600
+ code: input.invalidCode,
601
+ message: input.invalidEmptyArgumentMessage,
385
602
  span: input.span
386
603
  });
387
604
  return;
@@ -395,8 +612,8 @@ function parseAttributeArguments(context, input) {
395
612
  const first = namedSplit[0];
396
613
  if (!first) {
397
614
  pushDiagnostic(context, {
398
- code: "PSL_INVALID_ATTRIBUTE_SYNTAX",
399
- message: `Invalid named argument syntax "${trimmedPart}"`,
615
+ code: input.invalidCode,
616
+ message: input.invalidNamedArgumentMessage(trimmedPart),
400
617
  span: partSpan
401
618
  });
402
619
  return;
@@ -405,8 +622,8 @@ function parseAttributeArguments(context, input) {
405
622
  const rawValue = trimmedPart.slice(first.end + 1).trim();
406
623
  if (!name || rawValue.length === 0) {
407
624
  pushDiagnostic(context, {
408
- code: "PSL_INVALID_ATTRIBUTE_SYNTAX",
409
- message: `Invalid named argument syntax "${trimmedPart}"`,
625
+ code: input.invalidCode,
626
+ message: input.invalidNamedArgumentMessage(trimmedPart),
410
627
  span: partSpan
411
628
  });
412
629
  return;
@@ -435,16 +652,14 @@ function findBlockBounds(context, startLine) {
435
652
  for (let lineIndex = startLine; lineIndex < context.lines.length; lineIndex += 1) {
436
653
  const line = stripInlineComment(context.lines[lineIndex] ?? "");
437
654
  let quote = null;
438
- let previousCharacter = "";
439
- for (const character of line) {
655
+ for (let index = 0; index < line.length; index += 1) {
656
+ const character = line[index] ?? "";
440
657
  if (quote) {
441
- if (character === quote && previousCharacter !== "\\") quote = null;
442
- previousCharacter = character;
658
+ if (character === quote && !isQuoteEscaped(line, index)) quote = null;
443
659
  continue;
444
660
  }
445
661
  if (character === "\"" || character === "'") {
446
662
  quote = character;
447
- previousCharacter = character;
448
663
  continue;
449
664
  }
450
665
  if (character === "{") depth += 1;
@@ -456,7 +671,6 @@ function findBlockBounds(context, startLine) {
456
671
  closed: true
457
672
  };
458
673
  }
459
- previousCharacter = character;
460
674
  }
461
675
  }
462
676
  pushDiagnostic(context, {
@@ -474,12 +688,13 @@ function splitTopLevelSegments(value, separator) {
474
688
  const parts = [];
475
689
  let depthParen = 0;
476
690
  let depthBracket = 0;
691
+ let depthBrace = 0;
477
692
  let quote = null;
478
693
  let start = 0;
479
694
  for (let index = 0; index < value.length; index += 1) {
480
695
  const character = value[index] ?? "";
481
696
  if (quote) {
482
- if (character === quote && value[index - 1] !== "\\") quote = null;
697
+ if (character === quote && !isQuoteEscaped(value, index)) quote = null;
483
698
  continue;
484
699
  }
485
700
  if (character === "\"" || character === "'") {
@@ -502,7 +717,15 @@ function splitTopLevelSegments(value, separator) {
502
717
  depthBracket = Math.max(0, depthBracket - 1);
503
718
  continue;
504
719
  }
505
- if (character === separator && depthParen === 0 && depthBracket === 0) {
720
+ if (character === "{") {
721
+ depthBrace += 1;
722
+ continue;
723
+ }
724
+ if (character === "}") {
725
+ depthBrace = Math.max(0, depthBrace - 1);
726
+ continue;
727
+ }
728
+ if (character === separator && depthParen === 0 && depthBracket === 0 && depthBrace === 0) {
506
729
  parts.push({
507
730
  value: value.slice(start, index),
508
731
  start,
@@ -557,7 +780,7 @@ function extractAttributeTokensWithSpans(context, lineIndex, value, startColumn)
557
780
  while (index < value.length) {
558
781
  const char = value[index] ?? "";
559
782
  if (quote) {
560
- if (char === quote && value[index - 1] !== "\\") quote = null;
783
+ if (char === quote && !isQuoteEscaped(value, index)) quote = null;
561
784
  index += 1;
562
785
  continue;
563
786
  }
@@ -618,7 +841,7 @@ function stripInlineComment(line) {
618
841
  const current = line[index] ?? "";
619
842
  const next = line[index + 1] ?? "";
620
843
  if (quote) {
621
- if (current === quote && line[index - 1] !== "\\") quote = null;
844
+ if (current === quote && !isQuoteEscaped(line, index)) quote = null;
622
845
  continue;
623
846
  }
624
847
  if (current === "\"" || current === "'") {
@@ -678,4 +901,4 @@ function pushDiagnostic(context, diagnostic) {
678
901
 
679
902
  //#endregion
680
903
  export { parsePslDocument as t };
681
- //# sourceMappingURL=parser-Br4vqzd-.mjs.map
904
+ //# sourceMappingURL=parser-Cr64fw1E.mjs.map