@prisma-next/psl-parser 0.14.0-dev.5 → 0.14.0-dev.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -10
- package/dist/declarations-DR6To8_k.mjs +1060 -0
- package/dist/declarations-DR6To8_k.mjs.map +1 -0
- package/dist/format.d.mts +1 -1
- package/dist/format.mjs +2 -1
- package/dist/format.mjs.map +1 -1
- package/dist/index.d.mts +239 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +785 -3
- package/dist/index.mjs.map +1 -1
- package/dist/parse-3-vr14ej.mjs +626 -0
- package/dist/parse-3-vr14ej.mjs.map +1 -0
- package/dist/{parse-BjZ1LPe6.d.mts → parse-BazJr7Ye.d.mts} +128 -51
- package/dist/parse-BazJr7Ye.d.mts.map +1 -0
- package/dist/syntax.d.mts +20 -2
- package/dist/syntax.d.mts.map +1 -1
- package/dist/syntax.mjs +43 -2
- package/dist/syntax.mjs.map +1 -0
- package/package.json +9 -25
- package/src/attribute-spec/combinators/bool.ts +19 -0
- package/src/attribute-spec/combinators/diagnostic.ts +15 -0
- package/src/attribute-spec/combinators/entity-ref.ts +24 -0
- package/src/attribute-spec/combinators/field-ref.ts +36 -0
- package/src/attribute-spec/combinators/identifier.ts +16 -0
- package/src/attribute-spec/combinators/int.ts +19 -0
- package/src/attribute-spec/combinators/list.ts +43 -0
- package/src/attribute-spec/combinators/one-of.ts +29 -0
- package/src/attribute-spec/combinators/record.ts +43 -0
- package/src/attribute-spec/combinators/str.ts +19 -0
- package/src/attribute-spec/field-attribute.ts +27 -0
- package/src/attribute-spec/interpret.ts +154 -0
- package/src/attribute-spec/model-attribute.ts +27 -0
- package/src/attribute-spec/optional.ts +8 -0
- package/src/attribute-spec/types.ts +72 -0
- package/src/block-reconstruction.ts +139 -0
- package/src/exports/index.ts +57 -3
- package/src/exports/syntax.ts +25 -5
- package/src/extension-block.ts +107 -0
- package/src/parse.ts +23 -5
- package/src/resolve.ts +123 -0
- package/src/source-file.ts +25 -0
- package/src/symbol-table.ts +446 -0
- package/src/syntax/ast/attributes.ts +5 -6
- package/src/syntax/ast/declarations.ts +51 -26
- package/src/syntax/ast/expressions.ts +12 -13
- package/src/syntax/ast/identifier.ts +2 -3
- package/src/syntax/ast/qualified-name.ts +28 -19
- package/src/syntax/ast/type-annotation.ts +4 -5
- package/src/syntax/ast-helpers.ts +27 -3
- package/src/syntax/navigation.ts +55 -0
- package/src/syntax/red.ts +317 -42
- package/dist/parse-B_3gIEFd.mjs +0 -1421
- package/dist/parse-B_3gIEFd.mjs.map +0 -1
- package/dist/parse-BjZ1LPe6.d.mts.map +0 -1
- package/dist/parser-CaplKvRs.mjs +0 -1145
- package/dist/parser-CaplKvRs.mjs.map +0 -1
- package/dist/parser-Dfi3Wfdq.d.mts +0 -7
- package/dist/parser-Dfi3Wfdq.d.mts.map +0 -1
- package/dist/parser.d.mts +0 -3
- package/dist/parser.mjs +0 -3
- package/src/exports/parser.ts +0 -4
- package/src/parser.ts +0 -1642
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type { AuthoringPslBlockDescriptor } from '@prisma-next/framework-components/authoring';
|
|
2
|
+
import type {
|
|
3
|
+
PslBlockParam,
|
|
4
|
+
PslExtensionBlock,
|
|
5
|
+
PslExtensionBlockAttribute,
|
|
6
|
+
PslExtensionBlockParamValue,
|
|
7
|
+
PslSpan,
|
|
8
|
+
} from '@prisma-next/framework-components/psl-ast';
|
|
9
|
+
import type { ParseDiagnostic } from './parse';
|
|
10
|
+
import { nodePslSpan } from './resolve';
|
|
11
|
+
import type { SourceFile } from './source-file';
|
|
12
|
+
import type { GenericBlockDeclarationAst, KeyValuePairAst } from './syntax/ast/declarations';
|
|
13
|
+
import { ArrayLiteralAst, type ExpressionAst } from './syntax/ast/expressions';
|
|
14
|
+
import { printSyntax } from './syntax/ast-helpers';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Descriptor-free and unknown parameters become `value` stubs so validation can
|
|
18
|
+
* report them via key-set comparison. Duplicate member names are first-wins.
|
|
19
|
+
*/
|
|
20
|
+
export function reconstructExtensionBlock(
|
|
21
|
+
node: GenericBlockDeclarationAst,
|
|
22
|
+
descriptor: AuthoringPslBlockDescriptor | undefined,
|
|
23
|
+
sourceFile: SourceFile,
|
|
24
|
+
diagnostics: ParseDiagnostic[],
|
|
25
|
+
): PslExtensionBlock {
|
|
26
|
+
const keyword = node.keyword()?.text ?? '';
|
|
27
|
+
const blockName = node.name()?.name() ?? '';
|
|
28
|
+
|
|
29
|
+
const blockAttributes: PslExtensionBlockAttribute[] = [];
|
|
30
|
+
for (const attribute of node.attributes()) {
|
|
31
|
+
const name = attribute.name()?.path().join('.') ?? '';
|
|
32
|
+
const args = Array.from(attribute.argList()?.args() ?? [], (arg) => {
|
|
33
|
+
const value = arg.value();
|
|
34
|
+
return {
|
|
35
|
+
kind: 'positional' as const,
|
|
36
|
+
value: value === undefined ? '' : printSyntax(value.syntax).trim(),
|
|
37
|
+
span: nodePslSpan(arg.syntax, sourceFile),
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
blockAttributes.push({
|
|
41
|
+
name,
|
|
42
|
+
args,
|
|
43
|
+
span: nodePslSpan(attribute.syntax, sourceFile),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const parameters: Record<string, PslExtensionBlockParamValue> = {};
|
|
48
|
+
for (const entry of node.entries()) {
|
|
49
|
+
const key = entry.key()?.name();
|
|
50
|
+
if (key === undefined) continue;
|
|
51
|
+
const span = nodePslSpan(entry.syntax, sourceFile);
|
|
52
|
+
if (Object.hasOwn(parameters, key)) {
|
|
53
|
+
diagnostics.push({
|
|
54
|
+
code: 'PSL_EXTENSION_DUPLICATE_PARAMETER',
|
|
55
|
+
message: `Duplicate parameter "${key}" in "${keyword}" block "${blockName}"; first occurrence wins`,
|
|
56
|
+
range: {
|
|
57
|
+
start: sourceFile.positionAt(entry.syntax.offset),
|
|
58
|
+
end: sourceFile.positionAt(entry.syntax.offset + entry.syntax.green.textLength),
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
parameters[key] = reconstructParamValue(
|
|
64
|
+
entry,
|
|
65
|
+
descriptor?.parameters[key],
|
|
66
|
+
span,
|
|
67
|
+
sourceFile,
|
|
68
|
+
diagnostics,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
kind: descriptor?.discriminator ?? keyword,
|
|
74
|
+
name: blockName,
|
|
75
|
+
parameters,
|
|
76
|
+
blockAttributes,
|
|
77
|
+
span: nodePslSpan(node.syntax, sourceFile),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function reconstructParamValue(
|
|
82
|
+
entry: KeyValuePairAst,
|
|
83
|
+
param: PslBlockParam | undefined,
|
|
84
|
+
span: PslSpan,
|
|
85
|
+
sourceFile: SourceFile,
|
|
86
|
+
diagnostics: ParseDiagnostic[],
|
|
87
|
+
): PslExtensionBlockParamValue {
|
|
88
|
+
const value = entry.value();
|
|
89
|
+
if (value === undefined) {
|
|
90
|
+
return { kind: 'bare', span };
|
|
91
|
+
}
|
|
92
|
+
return reconstructFromExpression(value, param, span, sourceFile, diagnostics);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function reconstructFromExpression(
|
|
96
|
+
value: ExpressionAst,
|
|
97
|
+
param: PslBlockParam | undefined,
|
|
98
|
+
span: PslSpan,
|
|
99
|
+
sourceFile: SourceFile,
|
|
100
|
+
diagnostics?: ParseDiagnostic[],
|
|
101
|
+
): PslExtensionBlockParamValue {
|
|
102
|
+
const raw = printSyntax(value.syntax).trim();
|
|
103
|
+
if (param?.kind === 'list') {
|
|
104
|
+
const array = ArrayLiteralAst.cast(value.syntax);
|
|
105
|
+
if (!array) {
|
|
106
|
+
diagnostics?.push({
|
|
107
|
+
code: 'PSL_EXTENSION_INVALID_VALUE',
|
|
108
|
+
message: `List parameter expects an array literal, got ${raw}`,
|
|
109
|
+
range: {
|
|
110
|
+
start: sourceFile.positionAt(value.syntax.offset),
|
|
111
|
+
end: sourceFile.positionAt(value.syntax.offset + value.syntax.green.textLength),
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
return { kind: 'value', raw, span };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const items: PslExtensionBlockParamValue[] = [];
|
|
118
|
+
for (const element of array.elements()) {
|
|
119
|
+
items.push(
|
|
120
|
+
reconstructFromExpression(
|
|
121
|
+
element,
|
|
122
|
+
param.of,
|
|
123
|
+
nodePslSpan(element.syntax, sourceFile),
|
|
124
|
+
sourceFile,
|
|
125
|
+
diagnostics,
|
|
126
|
+
),
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
return { kind: 'list', items, span };
|
|
130
|
+
}
|
|
131
|
+
switch (param?.kind) {
|
|
132
|
+
case 'ref':
|
|
133
|
+
return { kind: 'ref', identifier: raw, span };
|
|
134
|
+
case 'option':
|
|
135
|
+
return { kind: 'option', token: raw, span };
|
|
136
|
+
default:
|
|
137
|
+
return { kind: 'value', raw, span };
|
|
138
|
+
}
|
|
139
|
+
}
|
package/src/exports/index.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
export type {
|
|
2
|
-
ParsePslDocumentInput,
|
|
3
|
-
ParsePslDocumentResult,
|
|
4
2
|
PslAttribute,
|
|
5
3
|
PslAttributeArgument,
|
|
6
4
|
PslAttributeNamedArgument,
|
|
@@ -38,4 +36,60 @@ export {
|
|
|
38
36
|
namespacePslExtensionBlocks,
|
|
39
37
|
} from '@prisma-next/framework-components/psl-ast';
|
|
40
38
|
export { getPositionalArgument, parseQuotedStringLiteral } from '../attribute-helpers';
|
|
41
|
-
export {
|
|
39
|
+
export { bool } from '../attribute-spec/combinators/bool';
|
|
40
|
+
export { leafDiagnostic } from '../attribute-spec/combinators/diagnostic';
|
|
41
|
+
export { entityRef } from '../attribute-spec/combinators/entity-ref';
|
|
42
|
+
export type { FieldRefArgType, FieldRefScope } from '../attribute-spec/combinators/field-ref';
|
|
43
|
+
export { fieldRef } from '../attribute-spec/combinators/field-ref';
|
|
44
|
+
export { identifier } from '../attribute-spec/combinators/identifier';
|
|
45
|
+
export { int } from '../attribute-spec/combinators/int';
|
|
46
|
+
export type { ListOptions } from '../attribute-spec/combinators/list';
|
|
47
|
+
export { list } from '../attribute-spec/combinators/list';
|
|
48
|
+
export { oneOf } from '../attribute-spec/combinators/one-of';
|
|
49
|
+
export { record } from '../attribute-spec/combinators/record';
|
|
50
|
+
export { str } from '../attribute-spec/combinators/str';
|
|
51
|
+
export { fieldAttribute } from '../attribute-spec/field-attribute';
|
|
52
|
+
export { interpretAttribute } from '../attribute-spec/interpret';
|
|
53
|
+
export { modelAttribute } from '../attribute-spec/model-attribute';
|
|
54
|
+
export { optional } from '../attribute-spec/optional';
|
|
55
|
+
export type {
|
|
56
|
+
ArgType,
|
|
57
|
+
AttributeLevel,
|
|
58
|
+
AttributeOut,
|
|
59
|
+
AttributeSpec,
|
|
60
|
+
InferAttr,
|
|
61
|
+
InterpretCtx,
|
|
62
|
+
NamedOut,
|
|
63
|
+
OptionalArgType,
|
|
64
|
+
OutOf,
|
|
65
|
+
Param,
|
|
66
|
+
PositionalParam,
|
|
67
|
+
PosOut,
|
|
68
|
+
} from '../attribute-spec/types';
|
|
69
|
+
export { findBlockDescriptor, validateExtensionBlockFromSymbol } from '../extension-block';
|
|
70
|
+
export {
|
|
71
|
+
keywordPslSpan,
|
|
72
|
+
nodePslSpan,
|
|
73
|
+
rangeToPslSpan,
|
|
74
|
+
readResolvedAttribute,
|
|
75
|
+
readResolvedAttributes,
|
|
76
|
+
readResolvedConstructorCall,
|
|
77
|
+
} from '../resolve';
|
|
78
|
+
export type {
|
|
79
|
+
BlockSymbol,
|
|
80
|
+
BuildSymbolTableOptions,
|
|
81
|
+
CompositeTypeSymbol,
|
|
82
|
+
FieldSymbol,
|
|
83
|
+
ModelSymbol,
|
|
84
|
+
NamespaceSymbol,
|
|
85
|
+
ResolvedAttribute,
|
|
86
|
+
ResolvedAttributeArg,
|
|
87
|
+
ResolvedNamedTypeBinding,
|
|
88
|
+
ResolvedTypeConstructorCall,
|
|
89
|
+
ScalarSymbol,
|
|
90
|
+
SymbolTable,
|
|
91
|
+
SymbolTableResult,
|
|
92
|
+
TopLevelScope,
|
|
93
|
+
TypeAliasSymbol,
|
|
94
|
+
} from '../symbol-table';
|
|
95
|
+
export { buildSymbolTable } from '../symbol-table';
|
package/src/exports/syntax.ts
CHANGED
|
@@ -7,7 +7,13 @@ export {
|
|
|
7
7
|
FieldAttributeAst,
|
|
8
8
|
ModelAttributeAst,
|
|
9
9
|
} from '../syntax/ast/attributes';
|
|
10
|
-
export type {
|
|
10
|
+
export type {
|
|
11
|
+
AttributeAst,
|
|
12
|
+
BlockMemberAst,
|
|
13
|
+
DeclarationAst,
|
|
14
|
+
GenericBlockMemberAst,
|
|
15
|
+
NamespaceMemberAst,
|
|
16
|
+
} from '../syntax/ast/declarations';
|
|
11
17
|
export {
|
|
12
18
|
CompositeTypeDeclarationAst,
|
|
13
19
|
DocumentAst,
|
|
@@ -35,12 +41,26 @@ export {
|
|
|
35
41
|
export { IdentifierAst } from '../syntax/ast/identifier';
|
|
36
42
|
export { QualifiedNameAst } from '../syntax/ast/qualified-name';
|
|
37
43
|
export { TypeAnnotationAst } from '../syntax/ast/type-annotation';
|
|
38
|
-
export type { AstNode } from '../syntax/ast-helpers';
|
|
39
|
-
export {
|
|
44
|
+
export type { AstNode, BracedBlock } from '../syntax/ast-helpers';
|
|
45
|
+
export {
|
|
46
|
+
any,
|
|
47
|
+
filterChildren,
|
|
48
|
+
findChildToken,
|
|
49
|
+
findFirstChild,
|
|
50
|
+
printSyntax,
|
|
51
|
+
} from '../syntax/ast-helpers';
|
|
40
52
|
export type { GreenElement, GreenNode, GreenToken } from '../syntax/green';
|
|
41
53
|
export { greenNode, greenToken } from '../syntax/green';
|
|
42
54
|
export { GreenNodeBuilder } from '../syntax/green-builder';
|
|
55
|
+
// Navigation helpers
|
|
56
|
+
export type { Direction } from '../syntax/navigation';
|
|
57
|
+
export {
|
|
58
|
+
isTrivia,
|
|
59
|
+
isTriviaKind,
|
|
60
|
+
nonTriviaSibling,
|
|
61
|
+
skipTriviaToken,
|
|
62
|
+
} from '../syntax/navigation';
|
|
43
63
|
// Red layer
|
|
44
|
-
export type { SyntaxElement
|
|
45
|
-
export { createSyntaxTree, SyntaxNode } from '../syntax/red';
|
|
64
|
+
export type { SyntaxElement } from '../syntax/red';
|
|
65
|
+
export { createSyntaxTree, SyntaxNode, SyntaxToken, TokenAtOffset } from '../syntax/red';
|
|
46
66
|
export type { SyntaxKind } from '../syntax/syntax-kind';
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AuthoringPslBlockDescriptor,
|
|
3
|
+
type AuthoringPslBlockDescriptorNamespace,
|
|
4
|
+
isAuthoringPslBlockDescriptor,
|
|
5
|
+
} from '@prisma-next/framework-components/authoring';
|
|
6
|
+
import type { CodecLookup } from '@prisma-next/framework-components/codec';
|
|
7
|
+
import {
|
|
8
|
+
makePslNamespace,
|
|
9
|
+
makePslNamespaceEntries,
|
|
10
|
+
type PslDiagnostic,
|
|
11
|
+
type PslModel,
|
|
12
|
+
type PslSpan,
|
|
13
|
+
UNSPECIFIED_PSL_NAMESPACE_ID,
|
|
14
|
+
validateExtensionBlock,
|
|
15
|
+
} from '@prisma-next/framework-components/psl-ast';
|
|
16
|
+
import type { SourceFile } from './source-file';
|
|
17
|
+
import type { BlockSymbol, ModelSymbol, SymbolTable } from './symbol-table';
|
|
18
|
+
|
|
19
|
+
export function findBlockDescriptor(
|
|
20
|
+
descriptors: AuthoringPslBlockDescriptorNamespace | undefined,
|
|
21
|
+
keyword: string,
|
|
22
|
+
): AuthoringPslBlockDescriptor | undefined {
|
|
23
|
+
if (descriptors === undefined) return undefined;
|
|
24
|
+
for (const value of Object.values(descriptors)) {
|
|
25
|
+
if (value === undefined) continue;
|
|
26
|
+
if (isAuthoringPslBlockDescriptor(value)) {
|
|
27
|
+
if (value.keyword === keyword) return value;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
const nested = findBlockDescriptor(value, keyword);
|
|
31
|
+
if (nested !== undefined) return nested;
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function validateExtensionBlockFromSymbol(input: {
|
|
37
|
+
readonly block: BlockSymbol;
|
|
38
|
+
readonly descriptor: AuthoringPslBlockDescriptor;
|
|
39
|
+
readonly symbolTable: SymbolTable;
|
|
40
|
+
readonly sourceFile: SourceFile;
|
|
41
|
+
readonly sourceId: string;
|
|
42
|
+
readonly codecLookup: CodecLookup;
|
|
43
|
+
}): readonly PslDiagnostic[] {
|
|
44
|
+
const refCtx = buildRefResolutionContext(input.symbolTable, input.block);
|
|
45
|
+
return validateExtensionBlock(
|
|
46
|
+
input.block.block,
|
|
47
|
+
input.descriptor,
|
|
48
|
+
input.sourceId,
|
|
49
|
+
input.codecLookup,
|
|
50
|
+
refCtx,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const ZERO_SPAN: PslSpan = {
|
|
55
|
+
start: { offset: 0, line: 1, column: 1 },
|
|
56
|
+
end: { offset: 0, line: 1, column: 1 },
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
function buildRefResolutionContext(
|
|
60
|
+
symbolTable: SymbolTable,
|
|
61
|
+
block: BlockSymbol,
|
|
62
|
+
): {
|
|
63
|
+
ownerNamespace: ReturnType<typeof makePslNamespace>;
|
|
64
|
+
allNamespaces: readonly ReturnType<typeof makePslNamespace>[];
|
|
65
|
+
} {
|
|
66
|
+
const unspecifiedNamespace = makeNamespace(
|
|
67
|
+
UNSPECIFIED_PSL_NAMESPACE_ID,
|
|
68
|
+
Object.values(symbolTable.topLevel.models),
|
|
69
|
+
);
|
|
70
|
+
const namedNamespaces = Object.values(symbolTable.topLevel.namespaces).map((namespace) =>
|
|
71
|
+
makeNamespace(namespace.name, Object.values(namespace.models)),
|
|
72
|
+
);
|
|
73
|
+
const allNamespaces = [unspecifiedNamespace, ...namedNamespaces];
|
|
74
|
+
const ownerNamespaceName = findOwnerNamespaceName(symbolTable, block);
|
|
75
|
+
const ownerNamespace =
|
|
76
|
+
allNamespaces.find((namespace) => namespace.name === ownerNamespaceName) ??
|
|
77
|
+
unspecifiedNamespace;
|
|
78
|
+
return { ownerNamespace, allNamespaces };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function makeNamespace(
|
|
82
|
+
name: string,
|
|
83
|
+
models: readonly ModelSymbol[],
|
|
84
|
+
): ReturnType<typeof makePslNamespace> {
|
|
85
|
+
const modelStubs: PslModel[] = models.map((model) => ({
|
|
86
|
+
kind: 'model',
|
|
87
|
+
name: model.name,
|
|
88
|
+
fields: [],
|
|
89
|
+
attributes: [],
|
|
90
|
+
span: ZERO_SPAN,
|
|
91
|
+
}));
|
|
92
|
+
return makePslNamespace({
|
|
93
|
+
kind: 'namespace',
|
|
94
|
+
name,
|
|
95
|
+
entries: makePslNamespaceEntries(modelStubs, [], []),
|
|
96
|
+
span: ZERO_SPAN,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function findOwnerNamespaceName(symbolTable: SymbolTable, block: BlockSymbol): string {
|
|
101
|
+
for (const namespace of Object.values(symbolTable.topLevel.namespaces)) {
|
|
102
|
+
if (Object.values(namespace.blocks).some((candidate) => candidate === block)) {
|
|
103
|
+
return namespace.name;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return UNSPECIFIED_PSL_NAMESPACE_ID;
|
|
107
|
+
}
|
package/src/parse.ts
CHANGED
|
@@ -399,14 +399,24 @@ function parseParenArgs(cursor: Cursor): void {
|
|
|
399
399
|
}
|
|
400
400
|
}
|
|
401
401
|
|
|
402
|
-
export function parseAttributeArg(cursor: Cursor):
|
|
402
|
+
export function parseAttributeArg(cursor: Cursor): void {
|
|
403
|
+
const kind = cursor.peekKind();
|
|
404
|
+
if (
|
|
405
|
+
kind !== 'Ident' &&
|
|
406
|
+
kind !== 'StringLiteral' &&
|
|
407
|
+
kind !== 'NumberLiteral' &&
|
|
408
|
+
kind !== 'LBracket' &&
|
|
409
|
+
kind !== 'LBrace'
|
|
410
|
+
) {
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
403
413
|
cursor.startNode('AttributeArg');
|
|
404
414
|
if (cursor.peekKind() === 'Ident' && cursor.peekKind(1) === 'Colon') {
|
|
405
415
|
parseIdentifier(cursor);
|
|
406
416
|
cursor.bump();
|
|
407
417
|
}
|
|
408
418
|
parseArgValue(cursor);
|
|
409
|
-
|
|
419
|
+
cursor.finishNode();
|
|
410
420
|
}
|
|
411
421
|
|
|
412
422
|
function parseArgValue(cursor: Cursor): void {
|
|
@@ -435,8 +445,16 @@ export function parseAttribute(cursor: Cursor): GreenNode {
|
|
|
435
445
|
return cursor.finishNode();
|
|
436
446
|
}
|
|
437
447
|
|
|
438
|
-
/**
|
|
439
|
-
|
|
448
|
+
/**
|
|
449
|
+
* A type annotation: `QualifiedName (argList)? ([])? (?)?`, e.g.
|
|
450
|
+
* `pgvector.Vector(1536)[]?`. When the field has no type, no node is emitted —
|
|
451
|
+
* a missing type is the absence of a `TypeAnnotation`, not a zero-width one.
|
|
452
|
+
*/
|
|
453
|
+
export function parseTypeAnnotation(cursor: Cursor): void {
|
|
454
|
+
const kind = cursor.peekKind();
|
|
455
|
+
if (kind !== 'Ident' && kind !== 'LBracket' && kind !== 'Question') {
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
440
458
|
cursor.startNode('TypeAnnotation');
|
|
441
459
|
if (cursor.peekKind() === 'Ident') {
|
|
442
460
|
parseQualifiedName(cursor);
|
|
@@ -453,7 +471,7 @@ export function parseTypeAnnotation(cursor: Cursor): GreenNode {
|
|
|
453
471
|
if (cursor.peekKind() === 'Question') {
|
|
454
472
|
cursor.bump();
|
|
455
473
|
}
|
|
456
|
-
|
|
474
|
+
cursor.finishNode();
|
|
457
475
|
}
|
|
458
476
|
|
|
459
477
|
type MemberParser = (cursor: Cursor) => void;
|
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
|
+
}
|
package/src/source-file.ts
CHANGED
|
@@ -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);
|