@prisma-next/psl-parser 0.0.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.
@@ -0,0 +1,681 @@
1
+ import { ifDefined } from "@prisma-next/utils/defined";
2
+
3
+ //#region src/parser.ts
4
+ const SCALAR_TYPES = new Set([
5
+ "String",
6
+ "Boolean",
7
+ "Int",
8
+ "BigInt",
9
+ "Float",
10
+ "Decimal",
11
+ "DateTime",
12
+ "Json",
13
+ "Bytes"
14
+ ]);
15
+ function parsePslDocument(input) {
16
+ const normalizedSchema = input.schema.replaceAll("\r\n", "\n");
17
+ const lines = normalizedSchema.split("\n");
18
+ const lineOffsets = computeLineOffsets(normalizedSchema);
19
+ const diagnostics = [];
20
+ const context = {
21
+ schema: normalizedSchema,
22
+ sourceId: input.sourceId,
23
+ lines,
24
+ lineOffsets,
25
+ diagnostics
26
+ };
27
+ const models = [];
28
+ const enums = [];
29
+ let typesBlock;
30
+ let lineIndex = 0;
31
+ while (lineIndex < lines.length) {
32
+ const line = stripInlineComment(lines[lineIndex] ?? "").trim();
33
+ if (line.length === 0) {
34
+ lineIndex += 1;
35
+ continue;
36
+ }
37
+ const modelMatch = line.match(/^model\s+([A-Za-z_]\w*)\s*\{$/);
38
+ if (modelMatch) {
39
+ const bounds = findBlockBounds(context, lineIndex);
40
+ const name = modelMatch[1] ?? "";
41
+ if (name.length === 0) {
42
+ lineIndex = bounds.endLine + 1;
43
+ continue;
44
+ }
45
+ models.push(parseModelBlock(context, name, bounds));
46
+ lineIndex = bounds.endLine + 1;
47
+ continue;
48
+ }
49
+ const enumMatch = line.match(/^enum\s+([A-Za-z_]\w*)\s*\{$/);
50
+ if (enumMatch) {
51
+ const bounds = findBlockBounds(context, lineIndex);
52
+ const name = enumMatch[1] ?? "";
53
+ if (name.length === 0) {
54
+ lineIndex = bounds.endLine + 1;
55
+ continue;
56
+ }
57
+ enums.push(parseEnumBlock(context, name, bounds));
58
+ lineIndex = bounds.endLine + 1;
59
+ continue;
60
+ }
61
+ if (/^types\s*\{$/.test(line)) {
62
+ const bounds = findBlockBounds(context, lineIndex);
63
+ typesBlock = parseTypesBlock(context, bounds);
64
+ lineIndex = bounds.endLine + 1;
65
+ continue;
66
+ }
67
+ if (line.includes("{")) {
68
+ pushDiagnostic(context, {
69
+ code: "PSL_UNSUPPORTED_TOP_LEVEL_BLOCK",
70
+ message: `Unsupported top-level block "${line.split(/\s+/)[0] ?? "block"}"`,
71
+ span: createTrimmedLineSpan(context, lineIndex)
72
+ });
73
+ lineIndex = findBlockBounds(context, lineIndex).endLine + 1;
74
+ continue;
75
+ }
76
+ pushDiagnostic(context, {
77
+ code: "PSL_UNSUPPORTED_TOP_LEVEL_BLOCK",
78
+ message: `Unsupported top-level declaration "${line}"`,
79
+ span: createTrimmedLineSpan(context, lineIndex)
80
+ });
81
+ lineIndex += 1;
82
+ }
83
+ const namedTypeNames = new Set((typesBlock?.declarations ?? []).map((declaration) => declaration.name));
84
+ const modelNames = new Set(models.map((model) => model.name));
85
+ const enumNames = new Set(enums.map((enumBlock) => enumBlock.name));
86
+ for (const declaration of typesBlock?.declarations ?? []) {
87
+ if (SCALAR_TYPES.has(declaration.name)) {
88
+ pushDiagnostic(context, {
89
+ code: "PSL_INVALID_TYPES_MEMBER",
90
+ message: `Named type "${declaration.name}" conflicts with scalar type "${declaration.name}"`,
91
+ span: declaration.span
92
+ });
93
+ continue;
94
+ }
95
+ if (modelNames.has(declaration.name)) {
96
+ pushDiagnostic(context, {
97
+ code: "PSL_INVALID_TYPES_MEMBER",
98
+ message: `Named type "${declaration.name}" conflicts with model name "${declaration.name}"`,
99
+ span: declaration.span
100
+ });
101
+ continue;
102
+ }
103
+ if (enumNames.has(declaration.name)) pushDiagnostic(context, {
104
+ code: "PSL_INVALID_TYPES_MEMBER",
105
+ message: `Named type "${declaration.name}" conflicts with enum name "${declaration.name}"`,
106
+ span: declaration.span
107
+ });
108
+ }
109
+ const normalizedModels = models.map((model) => ({
110
+ ...model,
111
+ fields: model.fields.map((field) => {
112
+ 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;
114
+ return {
115
+ ...field,
116
+ typeRef: field.typeName
117
+ };
118
+ })
119
+ }));
120
+ return {
121
+ ast: {
122
+ kind: "document",
123
+ sourceId: input.sourceId,
124
+ models: normalizedModels,
125
+ enums,
126
+ ...ifDefined("types", typesBlock),
127
+ span: {
128
+ start: createPosition(context, 0, 0),
129
+ end: createPosition(context, Math.max(lines.length - 1, 0), (lines[Math.max(lines.length - 1, 0)] ?? "").length)
130
+ }
131
+ },
132
+ diagnostics,
133
+ ok: diagnostics.length === 0
134
+ };
135
+ }
136
+ function parseModelBlock(context, name, bounds) {
137
+ const fields = [];
138
+ const attributes = [];
139
+ for (let lineIndex = bounds.startLine + 1; lineIndex < bounds.endLine; lineIndex += 1) {
140
+ const line = stripInlineComment(context.lines[lineIndex] ?? "").trim();
141
+ if (line.length === 0) continue;
142
+ if (line.startsWith("@@")) {
143
+ const attribute = parseModelAttribute(context, line, lineIndex);
144
+ if (attribute) attributes.push(attribute);
145
+ continue;
146
+ }
147
+ const field = parseField(context, line, lineIndex);
148
+ if (field) fields.push(field);
149
+ }
150
+ return {
151
+ kind: "model",
152
+ name,
153
+ fields,
154
+ attributes,
155
+ span: createLineRangeSpan(context, bounds.startLine, bounds.endLine)
156
+ };
157
+ }
158
+ function parseEnumBlock(context, name, bounds) {
159
+ const values = [];
160
+ for (let lineIndex = bounds.startLine + 1; lineIndex < bounds.endLine; lineIndex += 1) {
161
+ const line = stripInlineComment(context.lines[lineIndex] ?? "").trim();
162
+ if (line.length === 0) continue;
163
+ const valueMatch = line.match(/^([A-Za-z_]\w*)$/);
164
+ if (!valueMatch) {
165
+ pushDiagnostic(context, {
166
+ code: "PSL_INVALID_ENUM_MEMBER",
167
+ message: `Invalid enum value declaration "${line}"`,
168
+ span: createTrimmedLineSpan(context, lineIndex)
169
+ });
170
+ continue;
171
+ }
172
+ values.push({
173
+ kind: "enumValue",
174
+ name: valueMatch[1] ?? "",
175
+ span: createTrimmedLineSpan(context, lineIndex)
176
+ });
177
+ }
178
+ return {
179
+ kind: "enum",
180
+ name,
181
+ values,
182
+ span: createLineRangeSpan(context, bounds.startLine, bounds.endLine)
183
+ };
184
+ }
185
+ function parseTypesBlock(context, bounds) {
186
+ const declarations = [];
187
+ for (let lineIndex = bounds.startLine + 1; lineIndex < bounds.endLine; lineIndex += 1) {
188
+ const raw = context.lines[lineIndex] ?? "";
189
+ const line = stripInlineComment(raw).trim();
190
+ if (line.length === 0) continue;
191
+ const declarationMatch = line.match(/^([A-Za-z_]\w*)\s*=\s*([A-Za-z_]\w*)(.*)$/);
192
+ if (!declarationMatch) {
193
+ pushDiagnostic(context, {
194
+ code: "PSL_INVALID_TYPES_MEMBER",
195
+ message: `Invalid types declaration "${line}"`,
196
+ span: createTrimmedLineSpan(context, lineIndex)
197
+ });
198
+ continue;
199
+ }
200
+ const declarationName = declarationMatch[1] ?? "";
201
+ const baseType = declarationMatch[2] ?? "";
202
+ const attributePart = declarationMatch[3] ?? "";
203
+ 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);
208
+ if (!attributeParse.ok) continue;
209
+ const attributes = attributeParse.tokens.map((token) => parseAttributeToken(context, {
210
+ token: token.text,
211
+ target: "namedType",
212
+ lineIndex,
213
+ span: token.span
214
+ })).filter((attribute) => Boolean(attribute));
215
+ declarations.push({
216
+ kind: "namedType",
217
+ name: declarationName,
218
+ baseType,
219
+ attributes,
220
+ span: createTrimmedLineSpan(context, lineIndex)
221
+ });
222
+ }
223
+ return {
224
+ kind: "types",
225
+ declarations,
226
+ span: createLineRangeSpan(context, bounds.startLine, bounds.endLine)
227
+ };
228
+ }
229
+ function parseModelAttribute(context, line, lineIndex) {
230
+ const tokenParse = extractAttributeTokensWithSpans(context, lineIndex, line, firstNonWhitespaceColumn(context.lines[lineIndex] ?? ""));
231
+ if (!tokenParse.ok || tokenParse.tokens.length !== 1) {
232
+ pushDiagnostic(context, {
233
+ code: "PSL_INVALID_ATTRIBUTE_SYNTAX",
234
+ message: `Invalid model attribute syntax "${line}"`,
235
+ span: createTrimmedLineSpan(context, lineIndex)
236
+ });
237
+ return;
238
+ }
239
+ const token = tokenParse.tokens[0];
240
+ if (!token) return;
241
+ return parseAttributeToken(context, {
242
+ token: token.text,
243
+ target: "model",
244
+ lineIndex,
245
+ span: token.span
246
+ });
247
+ }
248
+ function parseField(context, line, lineIndex) {
249
+ const fieldMatch = line.match(/^([A-Za-z_]\w*)\s+([A-Za-z_]\w*(?:\[\])?)(\?)?(.*)$/);
250
+ if (!fieldMatch) {
251
+ pushDiagnostic(context, {
252
+ code: "PSL_INVALID_MODEL_MEMBER",
253
+ message: `Invalid model member declaration "${line}"`,
254
+ span: createTrimmedLineSpan(context, lineIndex)
255
+ });
256
+ return;
257
+ }
258
+ 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 = [];
266
+ const trimmedStartColumn = firstNonWhitespaceColumn(context.lines[lineIndex] ?? "");
267
+ const attributeOffset = line.length - attributePart.length;
268
+ const attributeSource = attributePart.trimStart();
269
+ const leadingAttributeWhitespace = attributePart.length - attributeSource.length;
270
+ const tokenParse = extractAttributeTokensWithSpans(context, lineIndex, attributeSource, trimmedStartColumn + attributeOffset + leadingAttributeWhitespace);
271
+ if (!tokenParse.ok) return {
272
+ kind: "field",
273
+ name: fieldName,
274
+ typeName,
275
+ optional,
276
+ list,
277
+ attributes,
278
+ span: createTrimmedLineSpan(context, lineIndex)
279
+ };
280
+ for (const token of tokenParse.tokens) {
281
+ const parsed = parseAttributeToken(context, {
282
+ token: token.text,
283
+ target: "field",
284
+ lineIndex,
285
+ span: token.span
286
+ });
287
+ if (parsed) attributes.push(parsed);
288
+ }
289
+ return {
290
+ kind: "field",
291
+ name: fieldName,
292
+ typeName,
293
+ optional,
294
+ list,
295
+ attributes,
296
+ span: createTrimmedLineSpan(context, lineIndex)
297
+ };
298
+ }
299
+ function parseAttributeToken(context, input) {
300
+ const expectsModelPrefix = input.target === "model";
301
+ if (expectsModelPrefix && !input.token.startsWith("@@")) {
302
+ pushDiagnostic(context, {
303
+ code: "PSL_INVALID_ATTRIBUTE_SYNTAX",
304
+ message: `Model attribute "${input.token}" must use @@ prefix`,
305
+ span: input.span
306
+ });
307
+ return;
308
+ }
309
+ if (!expectsModelPrefix && !input.token.startsWith("@")) {
310
+ pushDiagnostic(context, {
311
+ code: "PSL_INVALID_ATTRIBUTE_SYNTAX",
312
+ message: `Attribute "${input.token}" must use @ prefix`,
313
+ span: input.span
314
+ });
315
+ return;
316
+ }
317
+ if (!expectsModelPrefix && input.token.startsWith("@@")) {
318
+ pushDiagnostic(context, {
319
+ code: "PSL_INVALID_ATTRIBUTE_SYNTAX",
320
+ message: `Attribute "${input.token}" is not valid in ${input.target} context`,
321
+ span: input.span
322
+ });
323
+ return;
324
+ }
325
+ const rawBody = expectsModelPrefix ? input.token.slice(2) : input.token.slice(1);
326
+ const openParen = rawBody.indexOf("(");
327
+ const closeParen = rawBody.lastIndexOf(")");
328
+ const hasArgs = openParen >= 0 || closeParen >= 0;
329
+ if (openParen >= 0 && closeParen === -1 || openParen === -1 && closeParen >= 0) {
330
+ pushDiagnostic(context, {
331
+ code: "PSL_INVALID_ATTRIBUTE_SYNTAX",
332
+ message: `Invalid attribute syntax "${input.token}"`,
333
+ span: input.span
334
+ });
335
+ return;
336
+ }
337
+ const name = (openParen >= 0 ? rawBody.slice(0, openParen) : rawBody).trim();
338
+ if (!/^[A-Za-z_][A-Za-z0-9_-]*(\.[A-Za-z_][A-Za-z0-9_-]*)*$/.test(name)) {
339
+ pushDiagnostic(context, {
340
+ code: "PSL_INVALID_ATTRIBUTE_SYNTAX",
341
+ message: `Invalid attribute name "${name || input.token}"`,
342
+ span: input.span
343
+ });
344
+ return;
345
+ }
346
+ let args = [];
347
+ if (hasArgs && openParen >= 0 && closeParen >= openParen) {
348
+ if (closeParen !== rawBody.length - 1) {
349
+ pushDiagnostic(context, {
350
+ code: "PSL_INVALID_ATTRIBUTE_SYNTAX",
351
+ message: `Invalid trailing syntax in attribute "${input.token}"`,
352
+ span: input.span
353
+ });
354
+ return;
355
+ }
356
+ const parsedArgs = parseAttributeArguments(context, {
357
+ argsRaw: rawBody.slice(openParen + 1, closeParen),
358
+ argsOffset: input.span.start.column - 1 + (expectsModelPrefix ? 2 : 1) + openParen + 1,
359
+ lineIndex: input.lineIndex,
360
+ token: input.token,
361
+ span: input.span
362
+ });
363
+ if (!parsedArgs) return;
364
+ args = parsedArgs;
365
+ }
366
+ return {
367
+ kind: "attribute",
368
+ target: input.target,
369
+ name,
370
+ args,
371
+ span: input.span
372
+ };
373
+ }
374
+ function parseAttributeArguments(context, input) {
375
+ if (input.argsRaw.trim().length === 0) return [];
376
+ const parts = splitTopLevelSegments(input.argsRaw, ",");
377
+ const args = [];
378
+ for (const part of parts) {
379
+ const original = part.value;
380
+ const trimmedPart = original.trim();
381
+ if (trimmedPart.length === 0) {
382
+ pushDiagnostic(context, {
383
+ code: "PSL_INVALID_ATTRIBUTE_SYNTAX",
384
+ message: `Invalid empty argument in attribute "${input.token}"`,
385
+ span: input.span
386
+ });
387
+ return;
388
+ }
389
+ const leadingWhitespace = original.length - original.trimStart().length;
390
+ const partStart = input.argsOffset + part.start + leadingWhitespace;
391
+ const partEnd = partStart + trimmedPart.length;
392
+ const partSpan = createInlineSpan(context, input.lineIndex, partStart, partEnd);
393
+ const namedSplit = splitTopLevelSegments(trimmedPart, ":");
394
+ if (namedSplit.length > 1) {
395
+ const first = namedSplit[0];
396
+ if (!first) {
397
+ pushDiagnostic(context, {
398
+ code: "PSL_INVALID_ATTRIBUTE_SYNTAX",
399
+ message: `Invalid named argument syntax "${trimmedPart}"`,
400
+ span: partSpan
401
+ });
402
+ return;
403
+ }
404
+ const name = first.value.trim();
405
+ const rawValue = trimmedPart.slice(first.end + 1).trim();
406
+ if (!name || rawValue.length === 0) {
407
+ pushDiagnostic(context, {
408
+ code: "PSL_INVALID_ATTRIBUTE_SYNTAX",
409
+ message: `Invalid named argument syntax "${trimmedPart}"`,
410
+ span: partSpan
411
+ });
412
+ return;
413
+ }
414
+ args.push({
415
+ kind: "named",
416
+ name,
417
+ value: normalizeAttributeArgumentValue(rawValue),
418
+ span: partSpan
419
+ });
420
+ continue;
421
+ }
422
+ args.push({
423
+ kind: "positional",
424
+ value: normalizeAttributeArgumentValue(trimmedPart),
425
+ span: partSpan
426
+ });
427
+ }
428
+ return args;
429
+ }
430
+ function normalizeAttributeArgumentValue(value) {
431
+ return value.trim();
432
+ }
433
+ function findBlockBounds(context, startLine) {
434
+ let depth = 0;
435
+ for (let lineIndex = startLine; lineIndex < context.lines.length; lineIndex += 1) {
436
+ const line = stripInlineComment(context.lines[lineIndex] ?? "");
437
+ let quote = null;
438
+ let previousCharacter = "";
439
+ for (const character of line) {
440
+ if (quote) {
441
+ if (character === quote && previousCharacter !== "\\") quote = null;
442
+ previousCharacter = character;
443
+ continue;
444
+ }
445
+ if (character === "\"" || character === "'") {
446
+ quote = character;
447
+ previousCharacter = character;
448
+ continue;
449
+ }
450
+ if (character === "{") depth += 1;
451
+ if (character === "}") {
452
+ depth -= 1;
453
+ if (depth === 0) return {
454
+ startLine,
455
+ endLine: lineIndex,
456
+ closed: true
457
+ };
458
+ }
459
+ previousCharacter = character;
460
+ }
461
+ }
462
+ pushDiagnostic(context, {
463
+ code: "PSL_UNTERMINATED_BLOCK",
464
+ message: "Unterminated block declaration",
465
+ span: createTrimmedLineSpan(context, startLine)
466
+ });
467
+ return {
468
+ startLine,
469
+ endLine: context.lines.length - 1,
470
+ closed: false
471
+ };
472
+ }
473
+ function splitTopLevelSegments(value, separator) {
474
+ const parts = [];
475
+ let depthParen = 0;
476
+ let depthBracket = 0;
477
+ let quote = null;
478
+ let start = 0;
479
+ for (let index = 0; index < value.length; index += 1) {
480
+ const character = value[index] ?? "";
481
+ if (quote) {
482
+ if (character === quote && value[index - 1] !== "\\") quote = null;
483
+ continue;
484
+ }
485
+ if (character === "\"" || character === "'") {
486
+ quote = character;
487
+ continue;
488
+ }
489
+ if (character === "(") {
490
+ depthParen += 1;
491
+ continue;
492
+ }
493
+ if (character === ")") {
494
+ depthParen = Math.max(0, depthParen - 1);
495
+ continue;
496
+ }
497
+ if (character === "[") {
498
+ depthBracket += 1;
499
+ continue;
500
+ }
501
+ if (character === "]") {
502
+ depthBracket = Math.max(0, depthBracket - 1);
503
+ continue;
504
+ }
505
+ if (character === separator && depthParen === 0 && depthBracket === 0) {
506
+ parts.push({
507
+ value: value.slice(start, index),
508
+ start,
509
+ end: index
510
+ });
511
+ start = index + 1;
512
+ }
513
+ }
514
+ parts.push({
515
+ value: value.slice(start),
516
+ start,
517
+ end: value.length
518
+ });
519
+ return parts;
520
+ }
521
+ function extractAttributeTokensWithSpans(context, lineIndex, value, startColumn) {
522
+ const tokens = [];
523
+ let index = 0;
524
+ while (index < value.length) {
525
+ while (index < value.length && /\s/.test(value[index] ?? "")) index += 1;
526
+ if (index >= value.length) break;
527
+ if (value[index] !== "@") {
528
+ pushDiagnostic(context, {
529
+ code: "PSL_INVALID_ATTRIBUTE_SYNTAX",
530
+ message: `Invalid attribute syntax "${value.trim()}"`,
531
+ span: createInlineSpan(context, lineIndex, startColumn + index, startColumn + value.length)
532
+ });
533
+ return {
534
+ ok: false,
535
+ tokens
536
+ };
537
+ }
538
+ const start = index;
539
+ index += 1;
540
+ if (value[index] === "@") index += 1;
541
+ const nameStart = index;
542
+ while (index < value.length && /[A-Za-z0-9_.-]/.test(value[index] ?? "")) index += 1;
543
+ if (index === nameStart) {
544
+ pushDiagnostic(context, {
545
+ code: "PSL_INVALID_ATTRIBUTE_SYNTAX",
546
+ message: `Invalid attribute syntax "${value.slice(start).trim()}"`,
547
+ span: createInlineSpan(context, lineIndex, startColumn + start, startColumn + value.length)
548
+ });
549
+ return {
550
+ ok: false,
551
+ tokens
552
+ };
553
+ }
554
+ if (value[index] === "(") {
555
+ let depth = 0;
556
+ let quote = null;
557
+ while (index < value.length) {
558
+ const char = value[index] ?? "";
559
+ if (quote) {
560
+ if (char === quote && value[index - 1] !== "\\") quote = null;
561
+ index += 1;
562
+ continue;
563
+ }
564
+ if (char === "\"" || char === "'") {
565
+ quote = char;
566
+ index += 1;
567
+ continue;
568
+ }
569
+ if (char === "(") depth += 1;
570
+ else if (char === ")") {
571
+ depth -= 1;
572
+ if (depth === 0) {
573
+ index += 1;
574
+ break;
575
+ }
576
+ }
577
+ index += 1;
578
+ }
579
+ if (depth !== 0) {
580
+ pushDiagnostic(context, {
581
+ code: "PSL_INVALID_ATTRIBUTE_SYNTAX",
582
+ message: `Unterminated attribute argument list in "${value.slice(start).trim()}"`,
583
+ span: createInlineSpan(context, lineIndex, startColumn + start, startColumn + value.length)
584
+ });
585
+ return {
586
+ ok: false,
587
+ tokens
588
+ };
589
+ }
590
+ }
591
+ const tokenText = value.slice(start, index).trim();
592
+ tokens.push({
593
+ text: tokenText,
594
+ span: createInlineSpan(context, lineIndex, startColumn + start, startColumn + index)
595
+ });
596
+ while (index < value.length && /\s/.test(value[index] ?? "")) index += 1;
597
+ if (index < value.length && value[index] !== "@") break;
598
+ }
599
+ if (index < value.length && value[index] !== "@") {
600
+ pushDiagnostic(context, {
601
+ code: "PSL_INVALID_ATTRIBUTE_SYNTAX",
602
+ message: `Invalid attribute syntax "${value.trim()}"`,
603
+ span: createInlineSpan(context, lineIndex, startColumn + index, startColumn + value.length)
604
+ });
605
+ return {
606
+ ok: false,
607
+ tokens
608
+ };
609
+ }
610
+ return {
611
+ ok: true,
612
+ tokens
613
+ };
614
+ }
615
+ function stripInlineComment(line) {
616
+ let quote = null;
617
+ for (let index = 0; index < line.length - 1; index += 1) {
618
+ const current = line[index] ?? "";
619
+ const next = line[index + 1] ?? "";
620
+ if (quote) {
621
+ if (current === quote && line[index - 1] !== "\\") quote = null;
622
+ continue;
623
+ }
624
+ if (current === "\"" || current === "'") {
625
+ quote = current;
626
+ continue;
627
+ }
628
+ if (current === "/" && next === "/") return line.slice(0, index);
629
+ }
630
+ return line;
631
+ }
632
+ function computeLineOffsets(schema) {
633
+ const offsets = [0];
634
+ for (let index = 0; index < schema.length; index += 1) if (schema[index] === "\n") offsets.push(index + 1);
635
+ return offsets;
636
+ }
637
+ function firstNonWhitespaceColumn(line) {
638
+ const first = line.search(/\S/);
639
+ return first === -1 ? 0 : first;
640
+ }
641
+ function createInlineSpan(context, lineIndex, startColumn, endColumn) {
642
+ return {
643
+ start: createPosition(context, lineIndex, startColumn),
644
+ end: createPosition(context, lineIndex, endColumn)
645
+ };
646
+ }
647
+ function createTrimmedLineSpan(context, lineIndex) {
648
+ const line = context.lines[lineIndex] ?? "";
649
+ return {
650
+ start: createPosition(context, lineIndex, firstNonWhitespaceColumn(line)),
651
+ end: createPosition(context, lineIndex, line.length)
652
+ };
653
+ }
654
+ function createLineRangeSpan(context, startLine, endLine) {
655
+ const startLineText = context.lines[startLine] ?? "";
656
+ const endLineText = context.lines[endLine] ?? "";
657
+ return {
658
+ start: createPosition(context, startLine, firstNonWhitespaceColumn(startLineText)),
659
+ end: createPosition(context, endLine, endLineText.length)
660
+ };
661
+ }
662
+ function createPosition(context, lineIndex, columnIndex) {
663
+ const clampedLineIndex = Math.max(0, Math.min(lineIndex, context.lineOffsets.length - 1));
664
+ const lineText = context.lines[clampedLineIndex] ?? "";
665
+ const clampedColumnIndex = Math.max(0, Math.min(columnIndex, lineText.length));
666
+ return {
667
+ offset: (context.lineOffsets[clampedLineIndex] ?? 0) + clampedColumnIndex,
668
+ line: clampedLineIndex + 1,
669
+ column: clampedColumnIndex + 1
670
+ };
671
+ }
672
+ function pushDiagnostic(context, diagnostic) {
673
+ context.diagnostics.push({
674
+ ...diagnostic,
675
+ sourceId: context.sourceId
676
+ });
677
+ }
678
+
679
+ //#endregion
680
+ export { parsePslDocument as t };
681
+ //# sourceMappingURL=parser-BP2RP4rk.mjs.map