@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 +2 -3
- package/dist/index.d.mts +9 -3
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +14 -2
- package/dist/index.mjs.map +1 -0
- package/dist/{parser-BgdIJNqs.d.mts → parser-B_U8b1dE.d.mts} +2 -2
- package/dist/parser-B_U8b1dE.d.mts.map +1 -0
- package/dist/{parser-Br4vqzd-.mjs → parser-Cr64fw1E.mjs} +268 -45
- package/dist/parser-Cr64fw1E.mjs.map +1 -0
- package/dist/parser.d.mts +1 -1
- package/dist/parser.mjs +1 -1
- package/dist/{types-CyfspkvF.d.mts → types-CYb3hCxS.d.mts} +26 -4
- package/dist/types-CYb3hCxS.d.mts.map +1 -0
- package/dist/types.d.mts +2 -2
- package/package.json +4 -4
- package/src/attribute-helpers.ts +13 -0
- package/src/exports/index.ts +3 -0
- package/src/exports/types.ts +1 -0
- package/src/parser.ts +385 -43
- package/src/types.ts +26 -2
- package/dist/parser-BgdIJNqs.d.mts.map +0 -1
- package/dist/parser-Br4vqzd-.mjs.map +0 -1
- package/dist/types-CyfspkvF.d.mts.map +0 -1
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<
|
|
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 `@
|
|
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 {
|
|
2
|
-
import { t as parsePslDocument } from "./parser-
|
|
3
|
-
|
|
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-
|
|
1
|
+
import { t as parsePslDocument } from "./parser-Cr64fw1E.mjs";
|
|
2
2
|
|
|
3
|
-
|
|
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-
|
|
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-
|
|
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*(
|
|
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
|
|
205
|
-
const
|
|
206
|
-
const
|
|
207
|
-
const
|
|
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+
|
|
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
|
|
260
|
-
const
|
|
261
|
-
const
|
|
262
|
-
const
|
|
263
|
-
const
|
|
264
|
-
const
|
|
265
|
-
const
|
|
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
|
|
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
|
|
301
|
-
|
|
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:
|
|
518
|
+
message: `${targetLabel} attribute "${input.token}" must use @@ prefix`,
|
|
305
519
|
span: input.span
|
|
306
520
|
});
|
|
307
521
|
return;
|
|
308
522
|
}
|
|
309
|
-
if (!
|
|
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 (!
|
|
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 =
|
|
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 =
|
|
570
|
+
const parsedArgs = parseArgumentList(context, {
|
|
357
571
|
argsRaw: rawBody.slice(openParen + 1, closeParen),
|
|
358
|
-
argsOffset: input.span.start.column - 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
|
|
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:
|
|
384
|
-
message:
|
|
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:
|
|
399
|
-
message:
|
|
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:
|
|
409
|
-
message:
|
|
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
|
|
439
|
-
|
|
655
|
+
for (let index = 0; index < line.length; index += 1) {
|
|
656
|
+
const character = line[index] ?? "";
|
|
440
657
|
if (quote) {
|
|
441
|
-
if (character === quote &&
|
|
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
|
|
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 ===
|
|
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
|
|
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
|
|
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-
|
|
904
|
+
//# sourceMappingURL=parser-Cr64fw1E.mjs.map
|