Erster Docker-Stand
This commit is contained in:
608
_node_modules/@mrleebo/prisma-ast/src/PrismaSchemaBuilder.ts
generated
Normal file
608
_node_modules/@mrleebo/prisma-ast/src/PrismaSchemaBuilder.ts
generated
Normal file
@@ -0,0 +1,608 @@
|
||||
import * as schema from './getSchema';
|
||||
import {
|
||||
isOneOfSchemaObjects,
|
||||
isSchemaField,
|
||||
isSchemaObject,
|
||||
} from './schemaUtils';
|
||||
import { PrintOptions, printSchema } from './printSchema';
|
||||
import * as finder from './finder';
|
||||
|
||||
/** Returns the function type Original with its return type changed to NewReturn. */
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type ReplaceReturnType<Original extends (...args: any) => any, NewReturn> = (
|
||||
...a: Parameters<Original>
|
||||
) => NewReturn;
|
||||
|
||||
/**
|
||||
* Methods with return values that do not propagate the builder should not have
|
||||
* their return value modified by the type replacement system below
|
||||
* */
|
||||
type ExtractKeys = 'getSchema' | 'getSubject' | 'getParent' | 'print';
|
||||
|
||||
/** These keys preserve the return value context that they were given */
|
||||
type NeutralKeys =
|
||||
| 'break'
|
||||
| 'comment'
|
||||
| 'attribute'
|
||||
| 'enumerator'
|
||||
| 'then'
|
||||
| 'findByType'
|
||||
| 'findAllByType';
|
||||
|
||||
/** Keys allowed when you call .datasource() or .generator() */
|
||||
type DatasourceOrGeneratorKeys = 'assignment';
|
||||
|
||||
/** Keys allowed when you call .enum("name") */
|
||||
type EnumKeys = 'enumerator';
|
||||
|
||||
/** Keys allowed when you call .field("name") */
|
||||
type FieldKeys = 'attribute' | 'removeAttribute';
|
||||
|
||||
/** Keys allowed when you call .model("name") */
|
||||
type BlockKeys = 'blockAttribute' | 'field' | 'removeField';
|
||||
|
||||
type PrismaSchemaFinderOptions = finder.ByTypeOptions & {
|
||||
within?: finder.ByTypeSourceObject[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Utility type for making the PrismaSchemaBuilder below readable:
|
||||
* Removes methods from the builder that are prohibited based on the context
|
||||
* the builder is in. For example, you can add fields to a model, but you can't
|
||||
* add fields to an enum or a datasource.
|
||||
*/
|
||||
type PrismaSchemaSubset<
|
||||
Universe extends keyof ConcretePrismaSchemaBuilder,
|
||||
Method
|
||||
> = ReplaceReturnType<
|
||||
ConcretePrismaSchemaBuilder[Universe],
|
||||
PrismaSchemaBuilder<Exclude<keyof ConcretePrismaSchemaBuilder, Method>>
|
||||
>;
|
||||
|
||||
/**
|
||||
* The brain of this whole operation: depending on the key of the method name
|
||||
* we receive, filter the available list of method calls the user can make to
|
||||
* prevent them from making invalid calls, such as builder.datasource().field()
|
||||
* */
|
||||
type PrismaSchemaBuilder<K extends keyof ConcretePrismaSchemaBuilder> = {
|
||||
[U in K]: U extends ExtractKeys
|
||||
? ConcretePrismaSchemaBuilder[U]
|
||||
: U extends NeutralKeys
|
||||
? ConcretePrismaSchemaBuilder[U] //ReplaceReturnType<ConcretePrismaSchemaBuilder[U], PrismaSchemaBuilder<K>>
|
||||
: U extends 'datasource'
|
||||
? PrismaSchemaSubset<U, 'datasource' | EnumKeys | FieldKeys | BlockKeys>
|
||||
: U extends 'generator'
|
||||
? PrismaSchemaSubset<U, EnumKeys | FieldKeys | BlockKeys>
|
||||
: U extends 'model'
|
||||
? PrismaSchemaSubset<U, DatasourceOrGeneratorKeys | EnumKeys | FieldKeys>
|
||||
: U extends 'view'
|
||||
? PrismaSchemaSubset<U, DatasourceOrGeneratorKeys | EnumKeys | FieldKeys>
|
||||
: U extends 'type'
|
||||
? PrismaSchemaSubset<U, DatasourceOrGeneratorKeys | EnumKeys | FieldKeys>
|
||||
: U extends 'field'
|
||||
? PrismaSchemaSubset<U, DatasourceOrGeneratorKeys | EnumKeys>
|
||||
: U extends 'removeField'
|
||||
? PrismaSchemaSubset<U, DatasourceOrGeneratorKeys | EnumKeys | FieldKeys>
|
||||
: U extends 'enum'
|
||||
? PrismaSchemaSubset<U, DatasourceOrGeneratorKeys | BlockKeys | FieldKeys>
|
||||
: U extends 'removeAttribute'
|
||||
? PrismaSchemaSubset<U, DatasourceOrGeneratorKeys | EnumKeys>
|
||||
: PrismaSchemaSubset<
|
||||
U,
|
||||
DatasourceOrGeneratorKeys | EnumKeys | FieldKeys | BlockKeys | 'comment'
|
||||
>;
|
||||
};
|
||||
|
||||
type Arg =
|
||||
| string
|
||||
| {
|
||||
name: string;
|
||||
function?: Arg[];
|
||||
};
|
||||
type Parent = schema.Block | undefined;
|
||||
type Subject = schema.Block | schema.Field | schema.Enumerator | undefined;
|
||||
|
||||
export class ConcretePrismaSchemaBuilder {
|
||||
private schema: schema.Schema;
|
||||
private _subject: Subject;
|
||||
private _parent: Parent;
|
||||
|
||||
constructor(source = '') {
|
||||
this.schema = schema.getSchema(source);
|
||||
}
|
||||
|
||||
/** Prints the schema out as a source string */
|
||||
print(options: PrintOptions = {}): string {
|
||||
return printSchema(this.schema, options);
|
||||
}
|
||||
|
||||
/** Returns the underlying schema object for more advanced use cases. */
|
||||
getSchema(): schema.Schema {
|
||||
return this.schema;
|
||||
}
|
||||
|
||||
/** Mutation Methods */
|
||||
|
||||
/** Adds or updates a generator block based on the name. */
|
||||
generator(name: string, provider = 'prisma-client-js'): this {
|
||||
const generator: schema.Generator =
|
||||
this.schema.list.reduce<schema.Generator>(
|
||||
(memo, block) =>
|
||||
block.type === 'generator' && block.name === name ? block : memo,
|
||||
{
|
||||
type: 'generator',
|
||||
name,
|
||||
assignments: [
|
||||
{ type: 'assignment', key: 'provider', value: `"${provider}"` },
|
||||
],
|
||||
}
|
||||
);
|
||||
|
||||
if (!this.schema.list.includes(generator)) this.schema.list.push(generator);
|
||||
this._subject = generator;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Removes something from the schema with the given name. */
|
||||
drop(name: string): this {
|
||||
const index = this.schema.list.findIndex(
|
||||
(block) => 'name' in block && block.name === name
|
||||
);
|
||||
if (index !== -1) this.schema.list.splice(index, 1);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Sets the datasource for the schema. */
|
||||
datasource(provider: string, url: string | { env: string }): this {
|
||||
const datasource: schema.Datasource = {
|
||||
type: 'datasource',
|
||||
name: 'db',
|
||||
assignments: [
|
||||
{
|
||||
type: 'assignment',
|
||||
key: 'url',
|
||||
value:
|
||||
typeof url === 'string'
|
||||
? `"${url}"`
|
||||
: { type: 'function', name: 'env', params: [`"${url.env}"`] },
|
||||
},
|
||||
{ type: 'assignment', key: 'provider', value: `"${provider}"` },
|
||||
],
|
||||
};
|
||||
const existingIndex = this.schema.list.findIndex(
|
||||
(block) => block.type === 'datasource'
|
||||
);
|
||||
this.schema.list.splice(
|
||||
existingIndex,
|
||||
existingIndex !== -1 ? 1 : 0,
|
||||
datasource
|
||||
);
|
||||
this._subject = datasource;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Adds or updates a model based on the name. Can be chained with .field() or .blockAttribute() to add to it. */
|
||||
model(name: string): this {
|
||||
const model = this.schema.list.reduce<schema.Model>(
|
||||
(memo, block) =>
|
||||
block.type === 'model' && block.name === name ? block : memo,
|
||||
{ type: 'model', name, properties: [] }
|
||||
);
|
||||
if (!this.schema.list.includes(model)) this.schema.list.push(model);
|
||||
this._subject = model;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Adds or updates a view based on the name. Can be chained with .field() or .blockAttribute() to add to it. */
|
||||
view(name: string): this {
|
||||
const view = this.schema.list.reduce<schema.View>(
|
||||
(memo, block) =>
|
||||
block.type === 'view' && block.name === name ? block : memo,
|
||||
{ type: 'view', name, properties: [] }
|
||||
);
|
||||
if (!this.schema.list.includes(view)) this.schema.list.push(view);
|
||||
this._subject = view;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Adds or updates a type based on the name. Can be chained with .field() or .blockAttribute() to add to it. */
|
||||
type(name: string): this {
|
||||
const type = this.schema.list.reduce<schema.Type>(
|
||||
(memo, block) =>
|
||||
block.type === 'type' && block.name === name ? block : memo,
|
||||
{ type: 'type', name, properties: [] }
|
||||
);
|
||||
if (!this.schema.list.includes(type)) this.schema.list.push(type);
|
||||
this._subject = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Adds or updates an enum based on the name. Can be chained with .enumerator() to add a value to it. */
|
||||
enum(name: string, enumeratorNames: string[] = []): this {
|
||||
const e = this.schema.list.reduce<schema.Enum>(
|
||||
(memo, block) =>
|
||||
block.type === 'enum' && block.name === name ? block : memo,
|
||||
{
|
||||
type: 'enum',
|
||||
name,
|
||||
enumerators: enumeratorNames.map((name) => ({
|
||||
type: 'enumerator',
|
||||
name,
|
||||
})),
|
||||
} satisfies schema.Enum
|
||||
);
|
||||
if (!this.schema.list.includes(e)) this.schema.list.push(e);
|
||||
this._subject = e;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Add an enum value to the current enum. */
|
||||
enumerator(value: string): this {
|
||||
const subject = this.getSubject<schema.Enum>();
|
||||
if (!subject || !('type' in subject) || subject.type !== 'enum') {
|
||||
throw new Error('Subject must be a prisma enum!');
|
||||
}
|
||||
|
||||
const enumerator = {
|
||||
type: 'enumerator',
|
||||
name: value,
|
||||
} satisfies schema.Enumerator;
|
||||
subject.enumerators.push(enumerator);
|
||||
this._parent = this._subject as Exclude<
|
||||
Subject,
|
||||
{ type: 'field' | 'enumerator' }
|
||||
>;
|
||||
this._subject = enumerator;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current subject, such as a model, field, or enum.
|
||||
* @example
|
||||
* builder.getModel('User').field('firstName').getSubject() // the firstName field
|
||||
* */
|
||||
private getSubject<S extends Subject>(): S {
|
||||
return this._subject as S;
|
||||
}
|
||||
|
||||
/** Returns the parent of the current subject when in a nested context. The parent of a field is its model or view. */
|
||||
private getParent<S extends Parent = schema.Object>(): S {
|
||||
return this._parent as S;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a block-level attribute to the current model.
|
||||
* @example
|
||||
* builder.model('Project')
|
||||
* .blockAttribute("map", "projects")
|
||||
* .blockAttribute("unique", ["firstName", "lastName"]) // @@unique([firstName, lastName])
|
||||
* */
|
||||
blockAttribute(
|
||||
name: string,
|
||||
args?: string | string[] | Record<string, schema.Value>
|
||||
): this {
|
||||
let subject = this.getSubject<schema.Object | schema.Enum>();
|
||||
if (subject.type !== 'enum' && !isSchemaObject(subject)) {
|
||||
const parent = this.getParent<schema.Object>();
|
||||
if (!isOneOfSchemaObjects(parent, ['model', 'view', 'type', 'enum']))
|
||||
throw new Error('Subject must be a prisma model, view, or type!');
|
||||
|
||||
subject = this._subject = parent;
|
||||
}
|
||||
|
||||
const attributeArgs = ((): schema.AttributeArgument[] => {
|
||||
if (!args) return [] as schema.AttributeArgument[];
|
||||
if (typeof args === 'string')
|
||||
return [{ type: 'attributeArgument', value: `"${args}"` }];
|
||||
if (Array.isArray(args))
|
||||
return [{ type: 'attributeArgument', value: { type: 'array', args } }];
|
||||
return Object.entries(args).map(([key, value]) => ({
|
||||
type: 'attributeArgument',
|
||||
value: { type: 'keyValue', key, value },
|
||||
}));
|
||||
})();
|
||||
|
||||
const property: schema.BlockAttribute = {
|
||||
type: 'attribute',
|
||||
kind: 'object',
|
||||
name,
|
||||
args: attributeArgs,
|
||||
};
|
||||
|
||||
if (subject.type === 'enum') {
|
||||
subject.enumerators.push(property);
|
||||
} else {
|
||||
subject.properties.push(property);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Adds an attribute to the current field. */
|
||||
attribute<T extends schema.Field>(
|
||||
name: string,
|
||||
args?: Arg[] | Record<string, string[]>
|
||||
): this {
|
||||
const parent = this.getParent();
|
||||
const subject = this.getSubject<T>();
|
||||
if (!isOneOfSchemaObjects(parent, ['model', 'view', 'type', 'enum'])) {
|
||||
throw new Error('Parent must be a prisma model or view!');
|
||||
}
|
||||
|
||||
if (!isSchemaField(subject)) {
|
||||
throw new Error('Subject must be a prisma field or enumerator!');
|
||||
}
|
||||
|
||||
if (!subject.attributes) subject.attributes = [];
|
||||
const attribute = subject.attributes.reduce<schema.Attribute>(
|
||||
(memo, attr) =>
|
||||
attr.type === 'attribute' &&
|
||||
`${attr.group ? `${attr.group}.` : ''}${attr.name}` === name
|
||||
? attr
|
||||
: memo,
|
||||
{
|
||||
type: 'attribute',
|
||||
kind: 'field',
|
||||
name,
|
||||
}
|
||||
);
|
||||
|
||||
if (Array.isArray(args)) {
|
||||
const mapArg = (arg: Arg): schema.Value | schema.Func => {
|
||||
return typeof arg === 'string'
|
||||
? arg
|
||||
: {
|
||||
type: 'function',
|
||||
name: arg.name,
|
||||
params: arg.function?.map(mapArg) ?? [],
|
||||
};
|
||||
};
|
||||
|
||||
if (args.length > 0)
|
||||
attribute.args = args.map((arg) => ({
|
||||
type: 'attributeArgument',
|
||||
value: mapArg(arg),
|
||||
}));
|
||||
} else if (typeof args === 'object') {
|
||||
attribute.args = Object.entries(args).map(([key, value]) => ({
|
||||
type: 'attributeArgument',
|
||||
value: { type: 'keyValue', key, value: { type: 'array', args: value } },
|
||||
}));
|
||||
}
|
||||
|
||||
if (!subject.attributes.includes(attribute))
|
||||
subject.attributes.push(attribute);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Remove an attribute from the current field */
|
||||
removeAttribute<T extends schema.Field>(name: string): this {
|
||||
const parent = this.getParent();
|
||||
const subject = this.getSubject<T>();
|
||||
if (!isSchemaObject(parent)) {
|
||||
throw new Error('Parent must be a prisma model or view!');
|
||||
}
|
||||
|
||||
if (!isSchemaField(subject)) {
|
||||
throw new Error('Subject must be a prisma field!');
|
||||
}
|
||||
|
||||
if (!subject.attributes) subject.attributes = [];
|
||||
subject.attributes = subject.attributes.filter(
|
||||
(attr) => !(attr.type === 'attribute' && attr.name === name)
|
||||
);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Add an assignment to a generator or datasource */
|
||||
assignment<T extends schema.Generator | schema.Datasource>(
|
||||
key: string,
|
||||
value: string
|
||||
): this {
|
||||
const subject = this.getSubject<T>();
|
||||
if (
|
||||
!subject ||
|
||||
!('type' in subject) ||
|
||||
!['generator', 'datasource'].includes(subject.type)
|
||||
)
|
||||
throw new Error('Subject must be a prisma generator or datasource!');
|
||||
|
||||
function tap<T>(subject: T, callback: (s: T) => void) {
|
||||
callback(subject);
|
||||
return subject;
|
||||
}
|
||||
|
||||
const assignment = subject.assignments.reduce<schema.Assignment>(
|
||||
(memo, assignment) =>
|
||||
assignment.type === 'assignment' && assignment.key === key
|
||||
? tap(assignment, (a) => {
|
||||
a.value = `"${value}"`;
|
||||
})
|
||||
: memo,
|
||||
{
|
||||
type: 'assignment',
|
||||
key,
|
||||
value: `"${value}"`,
|
||||
}
|
||||
);
|
||||
|
||||
if (!subject.assignments.includes(assignment))
|
||||
subject.assignments.push(assignment);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Finder Methods */
|
||||
|
||||
/**
|
||||
* Queries the block list for the given block type. Returns `null` if none
|
||||
* match. Throws an error if more than one match is found.
|
||||
* */
|
||||
findByType<const Match extends finder.ByTypeMatch>(
|
||||
typeToMatch: Match,
|
||||
{ within = this.schema.list, ...options }: PrismaSchemaFinderOptions
|
||||
): finder.FindByBlock<Match> | null {
|
||||
return finder.findByType(within, typeToMatch, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries the block list for the given block type. Returns an array of all
|
||||
* matching objects, and an empty array (`[]`) if none match.
|
||||
* */
|
||||
findAllByType<const Match extends finder.ByTypeMatch>(
|
||||
typeToMatch: Match,
|
||||
{ within = this.schema.list, ...options }: PrismaSchemaFinderOptions
|
||||
): Array<finder.FindByBlock<Match> | null> {
|
||||
return finder.findAllByType(within, typeToMatch, options);
|
||||
}
|
||||
|
||||
/** Internal Utilities */
|
||||
|
||||
private blockInsert(statement: schema.Break | schema.Comment): this {
|
||||
let subject = this.getSubject<schema.Block>();
|
||||
const allowed = [
|
||||
'datasource',
|
||||
'enum',
|
||||
'generator',
|
||||
'model',
|
||||
'view',
|
||||
'type',
|
||||
];
|
||||
if (!subject || !('type' in subject) || !allowed.includes(subject.type)) {
|
||||
const parent = this.getParent<schema.Block>();
|
||||
if (!parent || !('type' in parent) || !allowed.includes(parent.type)) {
|
||||
throw new Error('Subject must be a prisma block!');
|
||||
}
|
||||
|
||||
subject = this._subject = parent;
|
||||
}
|
||||
|
||||
switch (subject.type) {
|
||||
case 'datasource': {
|
||||
subject.assignments.push(statement);
|
||||
break;
|
||||
}
|
||||
case 'enum': {
|
||||
subject.enumerators.push(statement);
|
||||
break;
|
||||
}
|
||||
case 'generator': {
|
||||
subject.assignments.push(statement);
|
||||
break;
|
||||
}
|
||||
case 'model': {
|
||||
subject.properties.push(statement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Add a line break */
|
||||
break(): this {
|
||||
const lineBreak: schema.Break = { type: 'break' };
|
||||
return this.blockInsert(lineBreak);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a comment. Regular comments start with // and do not appear in the
|
||||
* prisma AST. Node comments start with /// and will appear in the AST,
|
||||
* affixed to the node that follows the comment.
|
||||
* */
|
||||
comment(text: string, node = false): this {
|
||||
const comment: schema.Comment = {
|
||||
type: 'comment',
|
||||
text: `//${node ? '/' : ''} ${text}`,
|
||||
};
|
||||
return this.blockInsert(comment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a comment to the schema. Regular comments start with // and do not appear in the
|
||||
* prisma AST. Node comments start with /// and will appear in the AST,
|
||||
* affixed to the node that follows the comment.
|
||||
* */
|
||||
schemaComment(text: string, node = false): this {
|
||||
const comment: schema.Comment = {
|
||||
type: 'comment',
|
||||
text: `//${node ? '/' : ''} ${text}`,
|
||||
};
|
||||
this.schema.list.push(comment);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds or updates a field in the current model. The field can be customized
|
||||
* further with one or more .attribute() calls.
|
||||
* */
|
||||
field(name: string, fieldType: string | schema.Func = 'String'): this {
|
||||
let subject = this.getSubject<schema.Object>();
|
||||
if (!isSchemaObject(subject)) {
|
||||
const parent = this.getParent<schema.Object>();
|
||||
if (!isSchemaObject(parent))
|
||||
throw new Error(
|
||||
'Subject must be a prisma model or view or composite type!'
|
||||
);
|
||||
|
||||
subject = this._subject = parent;
|
||||
}
|
||||
|
||||
const field = subject.properties.reduce<schema.Field>(
|
||||
(memo, block) =>
|
||||
block.type === 'field' && block.name === name ? block : memo,
|
||||
{
|
||||
type: 'field',
|
||||
name,
|
||||
fieldType,
|
||||
}
|
||||
);
|
||||
|
||||
if (!subject.properties.includes(field)) subject.properties.push(field);
|
||||
this._parent = subject;
|
||||
this._subject = field;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Drop a field from the current model or view or composite type. */
|
||||
removeField(name: string): this {
|
||||
let subject = this.getSubject<schema.Object>();
|
||||
if (!isSchemaObject(subject)) {
|
||||
const parent = this.getParent<schema.Object>();
|
||||
if (!isSchemaObject(parent))
|
||||
throw new Error(
|
||||
'Subject must be a prisma model or view or composite type!'
|
||||
);
|
||||
|
||||
subject = this._subject = parent;
|
||||
}
|
||||
|
||||
subject.properties = subject.properties.filter(
|
||||
(field) => !(field.type === 'field' && field.name === name)
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current subject, allowing for more advanced ways of
|
||||
* manipulating the schema.
|
||||
* */
|
||||
then<R extends NonNullable<Subject>>(
|
||||
callback: (subject: R) => unknown
|
||||
): this {
|
||||
callback(this._subject as R);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
export function createPrismaSchemaBuilder(
|
||||
source?: string
|
||||
): PrismaSchemaBuilder<
|
||||
Exclude<
|
||||
keyof ConcretePrismaSchemaBuilder,
|
||||
DatasourceOrGeneratorKeys | EnumKeys | FieldKeys | BlockKeys
|
||||
>
|
||||
> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return new ConcretePrismaSchemaBuilder(source) as any;
|
||||
}
|
||||
66
_node_modules/@mrleebo/prisma-ast/src/finder.ts
generated
Normal file
66
_node_modules/@mrleebo/prisma-ast/src/finder.ts
generated
Normal file
@@ -0,0 +1,66 @@
|
||||
import type * as schema from './getSchema';
|
||||
|
||||
export type ByTypeSourceObject =
|
||||
| schema.Block
|
||||
| schema.Enumerator
|
||||
| schema.Field
|
||||
| schema.Property
|
||||
| schema.Attribute
|
||||
| schema.Assignment;
|
||||
|
||||
export type ByTypeMatchObject = Exclude<
|
||||
ByTypeSourceObject,
|
||||
schema.Comment | schema.Break
|
||||
>;
|
||||
export type ByTypeMatch = ByTypeMatchObject['type'];
|
||||
export type ByTypeOptions = { name?: string | RegExp };
|
||||
export type FindByBlock<Match> = Extract<ByTypeMatchObject, { type: Match }>;
|
||||
|
||||
export const findByType = <const Match extends ByTypeMatch>(
|
||||
list: ByTypeSourceObject[],
|
||||
typeToMatch: Match,
|
||||
options: ByTypeOptions = {}
|
||||
): FindByBlock<Match> | null => {
|
||||
const [match, unexpected] = list.filter(findBy(typeToMatch, options));
|
||||
|
||||
if (!match) return null;
|
||||
|
||||
if (unexpected)
|
||||
throw new Error(`Found multiple blocks with [type=${typeToMatch}]`);
|
||||
|
||||
return match;
|
||||
};
|
||||
|
||||
export const findAllByType = <const Match extends ByTypeMatch>(
|
||||
list: ByTypeSourceObject[],
|
||||
typeToMatch: Match,
|
||||
options: ByTypeOptions = {}
|
||||
): Array<FindByBlock<Match>> => {
|
||||
return list.filter(findBy(typeToMatch, options));
|
||||
};
|
||||
|
||||
type NameOf<Match extends ByTypeMatch> = Extract<
|
||||
Match,
|
||||
Match extends 'assignment' ? 'key' : 'name'
|
||||
>;
|
||||
|
||||
const findBy =
|
||||
<Match extends ByTypeMatch, MatchName extends NameOf<Match>>(
|
||||
typeToMatch: Match,
|
||||
{ name }: ByTypeOptions = {}
|
||||
) =>
|
||||
(block: ByTypeSourceObject): block is FindByBlock<Match> => {
|
||||
if (name != null) {
|
||||
const nameAttribute = (
|
||||
typeToMatch === 'assignment' ? 'key' : 'name'
|
||||
) as MatchName;
|
||||
if (!(nameAttribute in block)) return false;
|
||||
const nameMatches =
|
||||
typeof name === 'string'
|
||||
? block[nameAttribute] === name
|
||||
: name.test(block[nameAttribute]);
|
||||
if (!nameMatches) return false;
|
||||
}
|
||||
|
||||
return block.type === typeToMatch;
|
||||
};
|
||||
27
_node_modules/@mrleebo/prisma-ast/src/getConfig.ts
generated
Normal file
27
_node_modules/@mrleebo/prisma-ast/src/getConfig.ts
generated
Normal file
@@ -0,0 +1,27 @@
|
||||
import type { IParserConfig } from 'chevrotain';
|
||||
import {
|
||||
lilconfigSync as configSync,
|
||||
type LilconfigResult as ConfigResultRaw,
|
||||
} from 'lilconfig';
|
||||
|
||||
export type PrismaAstParserConfig = Pick<IParserConfig, 'nodeLocationTracking'>;
|
||||
export interface PrismaAstConfig {
|
||||
parser: PrismaAstParserConfig;
|
||||
}
|
||||
|
||||
type ConfigResult<T> = Omit<ConfigResultRaw, 'config'> & {
|
||||
config: T;
|
||||
};
|
||||
|
||||
const defaultConfig: PrismaAstConfig = {
|
||||
parser: { nodeLocationTracking: 'none' },
|
||||
};
|
||||
|
||||
let config: PrismaAstConfig;
|
||||
export default function getConfig(): PrismaAstConfig {
|
||||
if (config != null) return config;
|
||||
|
||||
const result: ConfigResult<PrismaAstConfig> | null =
|
||||
configSync('prisma-ast').search();
|
||||
return (config = Object.assign(defaultConfig, result?.config));
|
||||
}
|
||||
186
_node_modules/@mrleebo/prisma-ast/src/getSchema.ts
generated
Normal file
186
_node_modules/@mrleebo/prisma-ast/src/getSchema.ts
generated
Normal file
@@ -0,0 +1,186 @@
|
||||
import { PrismaLexer } from './lexer';
|
||||
import { PrismaVisitor, defaultVisitor } from './visitor';
|
||||
import type { CstNodeLocation } from 'chevrotain';
|
||||
import { PrismaParser, defaultParser } from './parser';
|
||||
|
||||
/**
|
||||
* Parses a string containing a prisma schema's source code and returns an
|
||||
* object that represents the parsed data structure. You can make direct
|
||||
* modifications to the objects and arrays nested within, and then produce
|
||||
* a new prisma schema using printSchema().
|
||||
*
|
||||
* @example
|
||||
* const schema = getSchema(source)
|
||||
* // ... make changes to schema object ...
|
||||
* const changedSource = printSchema(schema)
|
||||
* */
|
||||
export function getSchema(
|
||||
source: string,
|
||||
options?: {
|
||||
parser: PrismaParser;
|
||||
visitor: PrismaVisitor;
|
||||
}
|
||||
): Schema {
|
||||
const lexingResult = PrismaLexer.tokenize(source);
|
||||
|
||||
const parser = options?.parser ?? defaultParser;
|
||||
parser.input = lexingResult.tokens;
|
||||
const cstNode = parser.schema();
|
||||
if (parser.errors.length > 0) throw parser.errors[0];
|
||||
|
||||
const visitor = options?.visitor ?? defaultVisitor;
|
||||
return visitor.visit(cstNode);
|
||||
}
|
||||
|
||||
export interface Schema {
|
||||
type: 'schema';
|
||||
list: Block[];
|
||||
}
|
||||
|
||||
export type Block =
|
||||
| Model
|
||||
| View
|
||||
| Datasource
|
||||
| Generator
|
||||
| Enum
|
||||
| Comment
|
||||
| Break
|
||||
| Type;
|
||||
|
||||
export interface Object {
|
||||
type: 'model' | 'view' | 'type';
|
||||
name: string;
|
||||
properties: Array<Property | Comment | Break>;
|
||||
}
|
||||
|
||||
export interface Model extends Object {
|
||||
type: 'model';
|
||||
location?: CstNodeLocation;
|
||||
}
|
||||
|
||||
export interface View extends Object {
|
||||
type: 'view';
|
||||
location?: CstNodeLocation;
|
||||
}
|
||||
|
||||
export interface Type extends Object {
|
||||
type: 'type';
|
||||
location?: CstNodeLocation;
|
||||
}
|
||||
|
||||
export interface Datasource {
|
||||
type: 'datasource';
|
||||
name: string;
|
||||
assignments: Array<Assignment | Comment | Break>;
|
||||
location?: CstNodeLocation;
|
||||
}
|
||||
|
||||
export interface Generator {
|
||||
type: 'generator';
|
||||
name: string;
|
||||
assignments: Array<Assignment | Comment | Break>;
|
||||
location?: CstNodeLocation;
|
||||
}
|
||||
|
||||
export interface Enum {
|
||||
type: 'enum';
|
||||
name: string;
|
||||
enumerators: Array<
|
||||
Enumerator | Comment | Break | BlockAttribute | GroupedAttribute
|
||||
>;
|
||||
location?: CstNodeLocation;
|
||||
}
|
||||
|
||||
export interface Comment {
|
||||
type: 'comment';
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface Break {
|
||||
type: 'break';
|
||||
}
|
||||
|
||||
export type Property = GroupedBlockAttribute | BlockAttribute | Field;
|
||||
|
||||
export interface Assignment {
|
||||
type: 'assignment';
|
||||
key: string;
|
||||
value: Value;
|
||||
}
|
||||
|
||||
export interface Enumerator {
|
||||
type: 'enumerator';
|
||||
name: string;
|
||||
value?: Value;
|
||||
attributes?: Attribute[];
|
||||
comment?: string;
|
||||
}
|
||||
|
||||
export interface BlockAttribute {
|
||||
type: 'attribute';
|
||||
kind: 'object' | 'view' | 'type';
|
||||
group?: string;
|
||||
name: string;
|
||||
args: AttributeArgument[];
|
||||
location?: CstNodeLocation;
|
||||
}
|
||||
|
||||
export type GroupedBlockAttribute = BlockAttribute & { group: string };
|
||||
|
||||
export interface Field {
|
||||
type: 'field';
|
||||
name: string;
|
||||
fieldType: string | Func;
|
||||
array?: boolean;
|
||||
optional?: boolean;
|
||||
attributes?: Attribute[];
|
||||
comment?: string;
|
||||
location?: CstNodeLocation;
|
||||
}
|
||||
|
||||
export type Attr =
|
||||
| Attribute
|
||||
| GroupedAttribute
|
||||
| BlockAttribute
|
||||
| GroupedBlockAttribute;
|
||||
|
||||
export interface Attribute {
|
||||
type: 'attribute';
|
||||
kind: 'field';
|
||||
group?: string;
|
||||
name: string;
|
||||
args?: AttributeArgument[];
|
||||
location?: CstNodeLocation;
|
||||
}
|
||||
|
||||
export type GroupedAttribute = Attribute & { group: string };
|
||||
|
||||
export interface AttributeArgument {
|
||||
type: 'attributeArgument';
|
||||
value: KeyValue | Value | Func;
|
||||
}
|
||||
|
||||
export interface KeyValue {
|
||||
type: 'keyValue';
|
||||
key: string;
|
||||
value: Value;
|
||||
}
|
||||
|
||||
export interface Func {
|
||||
type: 'function';
|
||||
name: string;
|
||||
params?: Value[];
|
||||
}
|
||||
|
||||
export interface RelationArray {
|
||||
type: 'array';
|
||||
args: string[];
|
||||
}
|
||||
|
||||
export type Value =
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| Func
|
||||
| RelationArray
|
||||
| Array<Value>;
|
||||
8
_node_modules/@mrleebo/prisma-ast/src/index.ts
generated
Normal file
8
_node_modules/@mrleebo/prisma-ast/src/index.ts
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
export * from './produceSchema';
|
||||
export * from './getSchema';
|
||||
export * from './printSchema';
|
||||
export * from './PrismaSchemaBuilder';
|
||||
export type { PrismaAstConfig } from './getConfig';
|
||||
export type { CstNodeLocation } from 'chevrotain';
|
||||
export { VisitorClassFactory } from './visitor';
|
||||
export { PrismaParser } from './parser';
|
||||
191
_node_modules/@mrleebo/prisma-ast/src/lexer.ts
generated
Normal file
191
_node_modules/@mrleebo/prisma-ast/src/lexer.ts
generated
Normal file
@@ -0,0 +1,191 @@
|
||||
import { createToken, Lexer, IMultiModeLexerDefinition } from 'chevrotain';
|
||||
|
||||
export const Identifier = createToken({
|
||||
name: 'Identifier',
|
||||
pattern: /[a-zA-Z][\w-]*/,
|
||||
});
|
||||
export const Datasource = createToken({
|
||||
name: 'Datasource',
|
||||
pattern: /datasource/,
|
||||
push_mode: 'block',
|
||||
});
|
||||
export const Generator = createToken({
|
||||
name: 'Generator',
|
||||
pattern: /generator/,
|
||||
push_mode: 'block',
|
||||
});
|
||||
export const Model = createToken({
|
||||
name: 'Model',
|
||||
pattern: /model/,
|
||||
push_mode: 'block',
|
||||
});
|
||||
export const View = createToken({
|
||||
name: 'View',
|
||||
pattern: /view/,
|
||||
push_mode: 'block',
|
||||
});
|
||||
export const Enum = createToken({
|
||||
name: 'Enum',
|
||||
pattern: /enum/,
|
||||
push_mode: 'block',
|
||||
});
|
||||
export const Type = createToken({
|
||||
name: 'Type',
|
||||
pattern: /type/,
|
||||
push_mode: 'block',
|
||||
});
|
||||
export const True = createToken({
|
||||
name: 'True',
|
||||
pattern: /true/,
|
||||
longer_alt: Identifier,
|
||||
});
|
||||
export const False = createToken({
|
||||
name: 'False',
|
||||
pattern: /false/,
|
||||
longer_alt: Identifier,
|
||||
});
|
||||
export const Null = createToken({
|
||||
name: 'Null',
|
||||
pattern: /null/,
|
||||
longer_alt: Identifier,
|
||||
});
|
||||
export const Comment = createToken({
|
||||
name: 'Comment',
|
||||
pattern: Lexer.NA,
|
||||
});
|
||||
|
||||
export const DocComment = createToken({
|
||||
name: 'DocComment',
|
||||
pattern: /\/\/\/[ \t]*(.*)/,
|
||||
categories: [Comment],
|
||||
});
|
||||
export const LineComment = createToken({
|
||||
name: 'LineComment',
|
||||
pattern: /\/\/[ \t]*(.*)/,
|
||||
categories: [Comment],
|
||||
});
|
||||
export const Attribute = createToken({
|
||||
name: 'Attribute',
|
||||
pattern: Lexer.NA,
|
||||
});
|
||||
export const BlockAttribute = createToken({
|
||||
name: 'BlockAttribute',
|
||||
pattern: /@@/,
|
||||
label: "'@@'",
|
||||
categories: [Attribute],
|
||||
});
|
||||
export const FieldAttribute = createToken({
|
||||
name: 'FieldAttribute',
|
||||
pattern: /@/,
|
||||
label: "'@'",
|
||||
categories: [Attribute],
|
||||
});
|
||||
export const Dot = createToken({
|
||||
name: 'Dot',
|
||||
pattern: /\./,
|
||||
label: "'.'",
|
||||
});
|
||||
export const QuestionMark = createToken({
|
||||
name: 'QuestionMark',
|
||||
pattern: /\?/,
|
||||
label: "'?'",
|
||||
});
|
||||
export const LCurly = createToken({
|
||||
name: 'LCurly',
|
||||
pattern: /{/,
|
||||
label: "'{'",
|
||||
});
|
||||
export const RCurly = createToken({
|
||||
name: 'RCurly',
|
||||
pattern: /}/,
|
||||
label: "'}'",
|
||||
pop_mode: true,
|
||||
});
|
||||
export const LRound = createToken({
|
||||
name: 'LRound',
|
||||
pattern: /\(/,
|
||||
label: "'('",
|
||||
});
|
||||
export const RRound = createToken({
|
||||
name: 'RRound',
|
||||
pattern: /\)/,
|
||||
label: "')'",
|
||||
});
|
||||
export const LSquare = createToken({
|
||||
name: 'LSquare',
|
||||
pattern: /\[/,
|
||||
label: "'['",
|
||||
});
|
||||
export const RSquare = createToken({
|
||||
name: 'RSquare',
|
||||
pattern: /\]/,
|
||||
label: "']'",
|
||||
});
|
||||
export const Comma = createToken({
|
||||
name: 'Comma',
|
||||
pattern: /,/,
|
||||
label: "','",
|
||||
});
|
||||
export const Colon = createToken({
|
||||
name: 'Colon',
|
||||
pattern: /:/,
|
||||
label: "':'",
|
||||
});
|
||||
export const Equals = createToken({
|
||||
name: 'Equals',
|
||||
pattern: /=/,
|
||||
label: "'='",
|
||||
});
|
||||
export const StringLiteral = createToken({
|
||||
name: 'StringLiteral',
|
||||
pattern: /"(:?[^\\"\n\r]|\\(:?[bfnrtv"\\/]|u[0-9a-fA-F]{4}))*"/,
|
||||
});
|
||||
export const NumberLiteral = createToken({
|
||||
name: 'NumberLiteral',
|
||||
pattern: /-?(0|[1-9]\d*)(\.\d+)?([eE][+-]?\d+)?/,
|
||||
});
|
||||
export const WhiteSpace = createToken({
|
||||
name: 'WhiteSpace',
|
||||
pattern: /\s+/,
|
||||
group: Lexer.SKIPPED,
|
||||
});
|
||||
export const LineBreak = createToken({
|
||||
name: 'LineBreak',
|
||||
pattern: /\n|\r\n/,
|
||||
line_breaks: true,
|
||||
label: 'LineBreak',
|
||||
});
|
||||
|
||||
const naTokens = [Comment, DocComment, LineComment, LineBreak, WhiteSpace];
|
||||
|
||||
export const multiModeTokens: IMultiModeLexerDefinition = {
|
||||
modes: {
|
||||
global: [...naTokens, Datasource, Generator, Model, View, Enum, Type],
|
||||
block: [
|
||||
...naTokens,
|
||||
Attribute,
|
||||
BlockAttribute,
|
||||
FieldAttribute,
|
||||
Dot,
|
||||
QuestionMark,
|
||||
LCurly,
|
||||
RCurly,
|
||||
LSquare,
|
||||
RSquare,
|
||||
LRound,
|
||||
RRound,
|
||||
Comma,
|
||||
Colon,
|
||||
Equals,
|
||||
True,
|
||||
False,
|
||||
Null,
|
||||
StringLiteral,
|
||||
NumberLiteral,
|
||||
Identifier,
|
||||
],
|
||||
},
|
||||
defaultMode: 'global',
|
||||
};
|
||||
|
||||
export const PrismaLexer = new Lexer(multiModeTokens);
|
||||
267
_node_modules/@mrleebo/prisma-ast/src/parser.ts
generated
Normal file
267
_node_modules/@mrleebo/prisma-ast/src/parser.ts
generated
Normal file
@@ -0,0 +1,267 @@
|
||||
import { CstParser } from 'chevrotain';
|
||||
import getConfig, { PrismaAstParserConfig } from './getConfig';
|
||||
import * as lexer from './lexer';
|
||||
|
||||
type ComponentType =
|
||||
| 'datasource'
|
||||
| 'generator'
|
||||
| 'model'
|
||||
| 'view'
|
||||
| 'enum'
|
||||
| 'type';
|
||||
export class PrismaParser extends CstParser {
|
||||
readonly config: PrismaAstParserConfig;
|
||||
|
||||
constructor(config: PrismaAstParserConfig) {
|
||||
super(lexer.multiModeTokens, config);
|
||||
this.performSelfAnalysis();
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
private break = this.RULE('break', () => {
|
||||
this.CONSUME1(lexer.LineBreak);
|
||||
this.CONSUME2(lexer.LineBreak);
|
||||
});
|
||||
|
||||
private keyedArg = this.RULE('keyedArg', () => {
|
||||
this.CONSUME(lexer.Identifier, { LABEL: 'keyName' });
|
||||
this.CONSUME(lexer.Colon);
|
||||
this.SUBRULE(this.value);
|
||||
});
|
||||
|
||||
private array = this.RULE('array', () => {
|
||||
this.CONSUME(lexer.LSquare);
|
||||
this.MANY_SEP({
|
||||
SEP: lexer.Comma,
|
||||
DEF: () => {
|
||||
this.SUBRULE(this.value);
|
||||
},
|
||||
});
|
||||
this.CONSUME(lexer.RSquare);
|
||||
});
|
||||
|
||||
private func = this.RULE('func', () => {
|
||||
this.CONSUME(lexer.Identifier, { LABEL: 'funcName' });
|
||||
this.CONSUME(lexer.LRound);
|
||||
this.MANY_SEP({
|
||||
SEP: lexer.Comma,
|
||||
DEF: () => {
|
||||
this.OR([
|
||||
{ ALT: () => this.SUBRULE(this.keyedArg) },
|
||||
{ ALT: () => this.SUBRULE(this.value) },
|
||||
]);
|
||||
},
|
||||
});
|
||||
this.CONSUME(lexer.RRound);
|
||||
});
|
||||
|
||||
private value = this.RULE('value', () => {
|
||||
this.OR([
|
||||
{ ALT: () => this.CONSUME(lexer.StringLiteral, { LABEL: 'value' }) },
|
||||
{ ALT: () => this.CONSUME(lexer.NumberLiteral, { LABEL: 'value' }) },
|
||||
{ ALT: () => this.SUBRULE(this.array, { LABEL: 'value' }) },
|
||||
{ ALT: () => this.SUBRULE(this.func, { LABEL: 'value' }) },
|
||||
{ ALT: () => this.CONSUME(lexer.True, { LABEL: 'value' }) },
|
||||
{ ALT: () => this.CONSUME(lexer.False, { LABEL: 'value' }) },
|
||||
{ ALT: () => this.CONSUME(lexer.Null, { LABEL: 'value' }) },
|
||||
{ ALT: () => this.CONSUME(lexer.Identifier, { LABEL: 'value' }) },
|
||||
]);
|
||||
});
|
||||
|
||||
private property = this.RULE('property', () => {
|
||||
this.CONSUME(lexer.Identifier, { LABEL: 'propertyName' });
|
||||
this.CONSUME(lexer.Equals);
|
||||
this.SUBRULE(this.value, { LABEL: 'propertyValue' });
|
||||
});
|
||||
|
||||
private assignment = this.RULE('assignment', () => {
|
||||
this.CONSUME(lexer.Identifier, { LABEL: 'assignmentName' });
|
||||
this.CONSUME(lexer.Equals);
|
||||
this.SUBRULE(this.value, { LABEL: 'assignmentValue' });
|
||||
});
|
||||
|
||||
private field = this.RULE('field', () => {
|
||||
this.CONSUME(lexer.Identifier, { LABEL: 'fieldName' });
|
||||
this.SUBRULE(this.value, { LABEL: 'fieldType' });
|
||||
this.OPTION1(() => {
|
||||
this.OR([
|
||||
{
|
||||
ALT: () => {
|
||||
this.CONSUME(lexer.LSquare, { LABEL: 'array' });
|
||||
this.CONSUME(lexer.RSquare, { LABEL: 'array' });
|
||||
},
|
||||
},
|
||||
{ ALT: () => this.CONSUME(lexer.QuestionMark, { LABEL: 'optional' }) },
|
||||
]);
|
||||
});
|
||||
this.MANY(() => {
|
||||
this.SUBRULE(this.fieldAttribute, { LABEL: 'attributeList' });
|
||||
});
|
||||
this.OPTION2(() => {
|
||||
this.CONSUME(lexer.Comment, { LABEL: 'comment' });
|
||||
});
|
||||
});
|
||||
|
||||
private block = this.RULE(
|
||||
'block',
|
||||
(
|
||||
options: {
|
||||
componentType?: ComponentType;
|
||||
} = {}
|
||||
) => {
|
||||
const { componentType } = options;
|
||||
const isEnum = componentType === 'enum';
|
||||
const isObject =
|
||||
componentType === 'model' ||
|
||||
componentType === 'view' ||
|
||||
componentType === 'type';
|
||||
|
||||
this.CONSUME(lexer.LCurly);
|
||||
this.CONSUME1(lexer.LineBreak);
|
||||
this.MANY(() => {
|
||||
this.OR([
|
||||
{ ALT: () => this.SUBRULE(this.comment, { LABEL: 'list' }) },
|
||||
{
|
||||
GATE: () => isObject,
|
||||
ALT: () => this.SUBRULE(this.property, { LABEL: 'list' }),
|
||||
},
|
||||
{ ALT: () => this.SUBRULE(this.blockAttribute, { LABEL: 'list' }) },
|
||||
{
|
||||
GATE: () => isObject,
|
||||
ALT: () => this.SUBRULE(this.field, { LABEL: 'list' }),
|
||||
},
|
||||
{
|
||||
GATE: () => isEnum,
|
||||
ALT: () => this.SUBRULE(this.enum, { LABEL: 'list' }),
|
||||
},
|
||||
{
|
||||
GATE: () => !isObject,
|
||||
ALT: () => this.SUBRULE(this.assignment, { LABEL: 'list' }),
|
||||
},
|
||||
{ ALT: () => this.SUBRULE(this.break, { LABEL: 'list' }) },
|
||||
{ ALT: () => this.CONSUME2(lexer.LineBreak) },
|
||||
]);
|
||||
});
|
||||
this.CONSUME(lexer.RCurly);
|
||||
}
|
||||
);
|
||||
|
||||
private enum = this.RULE('enum', () => {
|
||||
this.CONSUME(lexer.Identifier, { LABEL: 'enumName' });
|
||||
this.MANY(() => {
|
||||
this.SUBRULE(this.fieldAttribute, { LABEL: 'attributeList' });
|
||||
});
|
||||
this.OPTION(() => {
|
||||
this.CONSUME(lexer.Comment, { LABEL: 'comment' });
|
||||
});
|
||||
});
|
||||
|
||||
private fieldAttribute = this.RULE('fieldAttribute', () => {
|
||||
this.CONSUME(lexer.FieldAttribute, { LABEL: 'fieldAttribute' });
|
||||
this.OR([
|
||||
{
|
||||
ALT: () => {
|
||||
this.CONSUME1(lexer.Identifier, { LABEL: 'groupName' });
|
||||
this.CONSUME(lexer.Dot);
|
||||
this.CONSUME2(lexer.Identifier, { LABEL: 'attributeName' });
|
||||
},
|
||||
},
|
||||
{
|
||||
ALT: () => this.CONSUME(lexer.Identifier, { LABEL: 'attributeName' }),
|
||||
},
|
||||
]);
|
||||
|
||||
this.OPTION(() => {
|
||||
this.CONSUME(lexer.LRound);
|
||||
this.MANY_SEP({
|
||||
SEP: lexer.Comma,
|
||||
DEF: () => {
|
||||
this.SUBRULE(this.attributeArg);
|
||||
},
|
||||
});
|
||||
this.CONSUME(lexer.RRound);
|
||||
});
|
||||
});
|
||||
|
||||
private blockAttribute = this.RULE('blockAttribute', () => {
|
||||
this.CONSUME(lexer.BlockAttribute, { LABEL: 'blockAttribute' }),
|
||||
this.OR([
|
||||
{
|
||||
ALT: () => {
|
||||
this.CONSUME1(lexer.Identifier, { LABEL: 'groupName' });
|
||||
this.CONSUME(lexer.Dot);
|
||||
this.CONSUME2(lexer.Identifier, { LABEL: 'attributeName' });
|
||||
},
|
||||
},
|
||||
{
|
||||
ALT: () => this.CONSUME(lexer.Identifier, { LABEL: 'attributeName' }),
|
||||
},
|
||||
]);
|
||||
|
||||
this.OPTION(() => {
|
||||
this.CONSUME(lexer.LRound);
|
||||
this.MANY_SEP({
|
||||
SEP: lexer.Comma,
|
||||
DEF: () => {
|
||||
this.SUBRULE(this.attributeArg);
|
||||
},
|
||||
});
|
||||
this.CONSUME(lexer.RRound);
|
||||
});
|
||||
});
|
||||
|
||||
private attributeArg = this.RULE('attributeArg', () => {
|
||||
this.OR([
|
||||
{
|
||||
ALT: () => this.SUBRULE(this.keyedArg, { LABEL: 'value' }),
|
||||
},
|
||||
{
|
||||
ALT: () => this.SUBRULE(this.value, { LABEL: 'value' }),
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
private component = this.RULE('component', () => {
|
||||
const type = this.OR1([
|
||||
{ ALT: () => this.CONSUME(lexer.Datasource, { LABEL: 'type' }) },
|
||||
{ ALT: () => this.CONSUME(lexer.Generator, { LABEL: 'type' }) },
|
||||
{ ALT: () => this.CONSUME(lexer.Model, { LABEL: 'type' }) },
|
||||
{ ALT: () => this.CONSUME(lexer.View, { LABEL: 'type' }) },
|
||||
{ ALT: () => this.CONSUME(lexer.Enum, { LABEL: 'type' }) },
|
||||
{ ALT: () => this.CONSUME(lexer.Type, { LABEL: 'type' }) },
|
||||
]);
|
||||
this.OR2([
|
||||
{
|
||||
ALT: () => {
|
||||
this.CONSUME1(lexer.Identifier, { LABEL: 'groupName' });
|
||||
this.CONSUME(lexer.Dot);
|
||||
this.CONSUME2(lexer.Identifier, { LABEL: 'componentName' });
|
||||
},
|
||||
},
|
||||
{
|
||||
ALT: () => this.CONSUME(lexer.Identifier, { LABEL: 'componentName' }),
|
||||
},
|
||||
]);
|
||||
|
||||
this.SUBRULE(this.block, {
|
||||
ARGS: [{ componentType: type.image as ComponentType }],
|
||||
});
|
||||
});
|
||||
|
||||
private comment = this.RULE('comment', () => {
|
||||
this.CONSUME(lexer.Comment, { LABEL: 'text' });
|
||||
});
|
||||
|
||||
public schema = this.RULE('schema', () => {
|
||||
this.MANY(() => {
|
||||
this.OR([
|
||||
{ ALT: () => this.SUBRULE(this.comment, { LABEL: 'list' }) },
|
||||
{ ALT: () => this.SUBRULE(this.component, { LABEL: 'list' }) },
|
||||
{ ALT: () => this.SUBRULE(this.break, { LABEL: 'list' }) },
|
||||
{ ALT: () => this.CONSUME(lexer.LineBreak) },
|
||||
]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export const defaultParser = new PrismaParser(getConfig().parser);
|
||||
389
_node_modules/@mrleebo/prisma-ast/src/printSchema.ts
generated
Normal file
389
_node_modules/@mrleebo/prisma-ast/src/printSchema.ts
generated
Normal file
@@ -0,0 +1,389 @@
|
||||
import * as Types from './getSchema';
|
||||
import { EOL } from 'os';
|
||||
import { schemaSorter } from './schemaSorter';
|
||||
|
||||
type Block = 'generator' | 'datasource' | 'model' | 'view' | 'enum' | 'type';
|
||||
|
||||
export interface PrintOptions {
|
||||
sort?: boolean;
|
||||
locales?: string | string[];
|
||||
sortOrder?: Block[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given schema object into a string representing the prisma
|
||||
* schema's source code. Optionally can take options to change the sort order
|
||||
* of the schema parts.
|
||||
* */
|
||||
export function printSchema(
|
||||
schema: Types.Schema,
|
||||
options: PrintOptions = {}
|
||||
): string {
|
||||
const { sort = false, locales = undefined, sortOrder = undefined } = options;
|
||||
let blocks = schema.list;
|
||||
if (sort) {
|
||||
// no point in preserving line breaks when re-sorting
|
||||
blocks = schema.list = blocks.filter((block) => block.type !== 'break');
|
||||
const sorter = schemaSorter(schema, locales, sortOrder);
|
||||
blocks.sort(sorter);
|
||||
}
|
||||
|
||||
return (
|
||||
blocks
|
||||
.map(printBlock)
|
||||
.filter(Boolean)
|
||||
.join(EOL)
|
||||
.replace(/(\r?\n\s*){3,}/g, EOL + EOL) + EOL
|
||||
);
|
||||
}
|
||||
|
||||
function printBlock(block: Types.Block): string {
|
||||
switch (block.type) {
|
||||
case 'comment':
|
||||
return printComment(block);
|
||||
case 'datasource':
|
||||
return printDatasource(block);
|
||||
case 'enum':
|
||||
return printEnum(block);
|
||||
case 'generator':
|
||||
return printGenerator(block);
|
||||
case 'model':
|
||||
case 'view':
|
||||
case 'type':
|
||||
return printObject(block);
|
||||
case 'break':
|
||||
return printBreak();
|
||||
default:
|
||||
throw new Error(`Unrecognized block type`);
|
||||
}
|
||||
}
|
||||
|
||||
function printComment(comment: Types.Comment) {
|
||||
return comment.text;
|
||||
}
|
||||
|
||||
function printBreak() {
|
||||
return EOL;
|
||||
}
|
||||
|
||||
function printDatasource(db: Types.Datasource) {
|
||||
const children = computeAssignmentFormatting(db.assignments);
|
||||
|
||||
return `
|
||||
datasource ${db.name} {
|
||||
${children}
|
||||
}`;
|
||||
}
|
||||
|
||||
function printEnum(enumerator: Types.Enum) {
|
||||
const list: Array<
|
||||
| Types.Comment
|
||||
| Types.Break
|
||||
| Types.Enumerator
|
||||
| Types.BlockAttribute
|
||||
| Types.GroupedBlockAttribute
|
||||
| Types.GroupedAttribute
|
||||
> = enumerator.enumerators;
|
||||
const children = list
|
||||
.filter(Boolean)
|
||||
.map(printEnumerator)
|
||||
.join(`${EOL} `)
|
||||
.replace(/(\r?\n\s*){3,}/g, `${EOL + EOL} `);
|
||||
|
||||
return `
|
||||
enum ${enumerator.name} {
|
||||
${children}
|
||||
}`;
|
||||
}
|
||||
|
||||
function printEnumerator(
|
||||
enumerator:
|
||||
| Types.Enumerator
|
||||
| Types.Attribute
|
||||
| Types.Comment
|
||||
| Types.Break
|
||||
| Types.BlockAttribute
|
||||
| Types.GroupedBlockAttribute
|
||||
| Types.GroupedAttribute
|
||||
) {
|
||||
switch (enumerator.type) {
|
||||
case 'enumerator': {
|
||||
const attrs = enumerator.attributes
|
||||
? enumerator.attributes.map(printAttribute)
|
||||
: [];
|
||||
return [enumerator.name, ...attrs, enumerator.comment]
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
}
|
||||
case 'attribute':
|
||||
return printAttribute(enumerator);
|
||||
case 'comment':
|
||||
return printComment(enumerator);
|
||||
case 'break':
|
||||
return printBreak();
|
||||
default:
|
||||
throw new Error(`Unexpected enumerator type`);
|
||||
}
|
||||
}
|
||||
|
||||
function printGenerator(generator: Types.Generator) {
|
||||
const children = computeAssignmentFormatting(generator.assignments);
|
||||
|
||||
return `
|
||||
generator ${generator.name} {
|
||||
${children}
|
||||
}`;
|
||||
}
|
||||
|
||||
function printObject(object: Types.Object) {
|
||||
const props = [...object.properties];
|
||||
|
||||
// If block attributes are declared in the middle of the block, move them to
|
||||
// the bottom of the list.
|
||||
let blockAttributeMoved = false;
|
||||
props.sort((a, b) => {
|
||||
if (
|
||||
a.type === 'attribute' &&
|
||||
a.kind === 'object' &&
|
||||
(b.type !== 'attribute' ||
|
||||
(b.type === 'attribute' && b.kind !== 'object'))
|
||||
) {
|
||||
blockAttributeMoved = true;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (
|
||||
b.type === 'attribute' &&
|
||||
b.kind === 'object' &&
|
||||
(a.type !== 'attribute' ||
|
||||
(a.type === 'attribute' && a.kind !== 'object'))
|
||||
) {
|
||||
blockAttributeMoved = true;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
// Insert a break between the block attributes and the file if the block
|
||||
// attributes are too close to the model's fields
|
||||
const attrIndex = props.findIndex(
|
||||
(item) => item.type === 'attribute' && item.kind === 'object'
|
||||
);
|
||||
|
||||
const needsSpace = !['break', 'comment'].includes(props[attrIndex - 1]?.type);
|
||||
if (blockAttributeMoved && needsSpace) {
|
||||
props.splice(attrIndex, 0, { type: 'break' });
|
||||
}
|
||||
|
||||
const children = computePropertyFormatting(props);
|
||||
|
||||
return `
|
||||
${object.type} ${object.name} {
|
||||
${children}
|
||||
}`;
|
||||
}
|
||||
|
||||
function printAssignment(
|
||||
node: Types.Assignment | Types.Comment | Types.Break,
|
||||
keyLength = 0
|
||||
) {
|
||||
switch (node.type) {
|
||||
case 'comment':
|
||||
return printComment(node);
|
||||
case 'break':
|
||||
return printBreak();
|
||||
case 'assignment':
|
||||
return `${node.key.padEnd(keyLength)} = ${printValue(node.value)}`;
|
||||
default:
|
||||
throw new Error(`Unexpected assignment type`);
|
||||
}
|
||||
}
|
||||
|
||||
function printProperty(
|
||||
node: Types.Property | Types.Comment | Types.Break,
|
||||
nameLength = 0,
|
||||
typeLength = 0
|
||||
) {
|
||||
switch (node.type) {
|
||||
case 'attribute':
|
||||
return printAttribute(node);
|
||||
case 'field':
|
||||
return printField(node, nameLength, typeLength);
|
||||
case 'comment':
|
||||
return printComment(node);
|
||||
case 'break':
|
||||
return printBreak();
|
||||
default:
|
||||
throw new Error(`Unrecognized property type`);
|
||||
}
|
||||
}
|
||||
|
||||
function printAttribute(attribute: Types.Attribute | Types.BlockAttribute) {
|
||||
const args =
|
||||
attribute.args && attribute.args.length > 0
|
||||
? `(${attribute.args.map(printAttributeArg).filter(Boolean).join(', ')})`
|
||||
: '';
|
||||
|
||||
const name = [attribute.name];
|
||||
if (attribute.group) name.unshift(attribute.group);
|
||||
|
||||
return `${attribute.kind === 'field' ? '@' : '@@'}${name.join('.')}${args}`;
|
||||
}
|
||||
|
||||
function printAttributeArg(arg: Types.AttributeArgument) {
|
||||
return printValue(arg.value);
|
||||
}
|
||||
|
||||
function printField(field: Types.Field, nameLength = 0, typeLength = 0) {
|
||||
const name = field.name.padEnd(nameLength);
|
||||
const fieldType = printFieldType(field).padEnd(typeLength);
|
||||
const attrs = field.attributes ? field.attributes.map(printAttribute) : [];
|
||||
const comment = field.comment;
|
||||
return (
|
||||
[name, fieldType, ...attrs]
|
||||
.filter(Boolean)
|
||||
.join(' ')
|
||||
// comments ignore indents
|
||||
.trim() + (comment ? ` ${comment}` : '')
|
||||
);
|
||||
}
|
||||
|
||||
function printFieldType(field: Types.Field) {
|
||||
const suffix = field.array ? '[]' : field.optional ? '?' : '';
|
||||
|
||||
if (typeof field.fieldType === 'object') {
|
||||
switch (field.fieldType.type) {
|
||||
case 'function': {
|
||||
return `${printFunction(field.fieldType)}${suffix}`;
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unexpected field type`);
|
||||
}
|
||||
}
|
||||
|
||||
return `${field.fieldType}${suffix}`;
|
||||
}
|
||||
|
||||
function printFunction(func: Types.Func) {
|
||||
const params = func.params ? func.params.map(printValue) : '';
|
||||
return `${func.name}(${params})`;
|
||||
}
|
||||
|
||||
function printValue(value: Types.KeyValue | Types.Value): string {
|
||||
switch (typeof value) {
|
||||
case 'object': {
|
||||
if ('type' in value) {
|
||||
switch (value.type) {
|
||||
case 'keyValue':
|
||||
return `${value.key}: ${printValue(value.value)}`;
|
||||
case 'function':
|
||||
return printFunction(value);
|
||||
case 'array':
|
||||
return `[${
|
||||
value.args != null ? value.args.map(printValue).join(', ') : ''
|
||||
}]`;
|
||||
default:
|
||||
throw new Error(`Unexpected value type`);
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Unexpected object value`);
|
||||
}
|
||||
default:
|
||||
return String(value);
|
||||
}
|
||||
}
|
||||
|
||||
function computeAssignmentFormatting(
|
||||
list: Array<Types.Comment | Types.Break | Types.Assignment>
|
||||
) {
|
||||
let pos = 0;
|
||||
const listBlocks = list.reduce<Array<typeof list>>(
|
||||
(memo, current, index, arr) => {
|
||||
if (current.type === 'break') return memo;
|
||||
if (index > 0 && arr[index - 1].type === 'break') memo[++pos] = [];
|
||||
memo[pos].push(current);
|
||||
return memo;
|
||||
},
|
||||
[[]]
|
||||
);
|
||||
|
||||
const keyLengths = listBlocks.map((lists) =>
|
||||
lists.reduce(
|
||||
(max, current) =>
|
||||
Math.max(
|
||||
max,
|
||||
// perhaps someone more typescript-savy than I am can fix this
|
||||
current.type === 'assignment' ? current.key.length : 0
|
||||
),
|
||||
0
|
||||
)
|
||||
);
|
||||
|
||||
return list
|
||||
.map((item, index, arr) => {
|
||||
if (index > 0 && item.type !== 'break' && arr[index - 1].type === 'break')
|
||||
keyLengths.shift();
|
||||
return printAssignment(item, keyLengths[0]);
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join(`${EOL} `)
|
||||
.replace(/(\r?\n\s*){3,}/g, `${EOL + EOL} `);
|
||||
}
|
||||
|
||||
function computePropertyFormatting(
|
||||
list: Array<Types.Break | Types.Comment | Types.Property>
|
||||
) {
|
||||
let pos = 0;
|
||||
const listBlocks = list.reduce<Array<typeof list>>(
|
||||
(memo, current, index, arr) => {
|
||||
if (current.type === 'break') return memo;
|
||||
if (index > 0 && arr[index - 1].type === 'break') memo[++pos] = [];
|
||||
memo[pos].push(current);
|
||||
return memo;
|
||||
},
|
||||
[[]]
|
||||
);
|
||||
|
||||
const nameLengths = listBlocks.map((lists) =>
|
||||
lists.reduce(
|
||||
(max, current) =>
|
||||
Math.max(
|
||||
max,
|
||||
// perhaps someone more typescript-savy than I am can fix this
|
||||
current.type === 'field' ? current.name.length : 0
|
||||
),
|
||||
0
|
||||
)
|
||||
);
|
||||
|
||||
const typeLengths = listBlocks.map((lists) =>
|
||||
lists.reduce(
|
||||
(max, current) =>
|
||||
Math.max(
|
||||
max,
|
||||
// perhaps someone more typescript-savy than I am can fix this
|
||||
current.type === 'field' ? printFieldType(current).length : 0
|
||||
),
|
||||
0
|
||||
)
|
||||
);
|
||||
|
||||
return list
|
||||
.map((prop, index, arr) => {
|
||||
if (
|
||||
index > 0 &&
|
||||
prop.type !== 'break' &&
|
||||
arr[index - 1].type === 'break'
|
||||
) {
|
||||
nameLengths.shift();
|
||||
typeLengths.shift();
|
||||
}
|
||||
|
||||
return printProperty(prop, nameLengths[0], typeLengths[0]);
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join(`${EOL} `)
|
||||
.replace(/(\r?\n\s*){3,}/g, `${EOL + EOL} `);
|
||||
}
|
||||
19
_node_modules/@mrleebo/prisma-ast/src/produceSchema.ts
generated
Normal file
19
_node_modules/@mrleebo/prisma-ast/src/produceSchema.ts
generated
Normal file
@@ -0,0 +1,19 @@
|
||||
import { PrintOptions } from './printSchema';
|
||||
import { createPrismaSchemaBuilder } from './PrismaSchemaBuilder';
|
||||
|
||||
type Options = PrintOptions;
|
||||
|
||||
/**
|
||||
* Receives a prisma schema in the form of a string containing source code, and
|
||||
* a callback builder function. Use the builder to modify your schema as
|
||||
* desired. Returns the schema as a string with the modifications applied.
|
||||
* */
|
||||
export function produceSchema(
|
||||
source: string,
|
||||
producer: (builder: ReturnType<typeof createPrismaSchemaBuilder>) => void,
|
||||
options: Options = {}
|
||||
): string {
|
||||
const builder = createPrismaSchemaBuilder(source);
|
||||
producer(builder);
|
||||
return builder.print(options);
|
||||
}
|
||||
43
_node_modules/@mrleebo/prisma-ast/src/schemaSorter.ts
generated
Normal file
43
_node_modules/@mrleebo/prisma-ast/src/schemaSorter.ts
generated
Normal file
@@ -0,0 +1,43 @@
|
||||
import { Block, Schema } from './getSchema';
|
||||
|
||||
const unsorted = ['break', 'comment'];
|
||||
const defaultSortOrder = [
|
||||
'generator',
|
||||
'datasource',
|
||||
'model',
|
||||
'view',
|
||||
'enum',
|
||||
'break',
|
||||
'comment',
|
||||
];
|
||||
|
||||
/** Sorts the schema parts, in the given order, and alphabetically for parts of the same type. */
|
||||
export const schemaSorter =
|
||||
(
|
||||
schema: Schema,
|
||||
locales?: string | string[],
|
||||
sortOrder: string[] = defaultSortOrder
|
||||
) =>
|
||||
(a: Block, b: Block): number => {
|
||||
// Preserve the position of comments and line breaks relative to their
|
||||
// position in the file, since when a re-sort happens it wouldn't be
|
||||
// clear whether a comment should affix to the object above or below it.
|
||||
const aUnsorted = unsorted.indexOf(a.type) !== -1;
|
||||
const bUnsorted = unsorted.indexOf(b.type) !== -1;
|
||||
|
||||
if (aUnsorted !== bUnsorted) {
|
||||
return schema.list.indexOf(a) - schema.list.indexOf(b);
|
||||
}
|
||||
|
||||
if (sortOrder !== defaultSortOrder)
|
||||
sortOrder = sortOrder.concat(defaultSortOrder);
|
||||
const typeIndex = sortOrder.indexOf(a.type) - sortOrder.indexOf(b.type);
|
||||
if (typeIndex !== 0) return typeIndex;
|
||||
|
||||
// Resolve ties using the name of object's name.
|
||||
if ('name' in a && 'name' in b)
|
||||
return a.name.localeCompare(b.name, locales);
|
||||
|
||||
// If all else fails, leave objects in their original position.
|
||||
return 0;
|
||||
};
|
||||
72
_node_modules/@mrleebo/prisma-ast/src/schemaUtils.ts
generated
Normal file
72
_node_modules/@mrleebo/prisma-ast/src/schemaUtils.ts
generated
Normal file
@@ -0,0 +1,72 @@
|
||||
import type { CstNode, IToken } from 'chevrotain';
|
||||
import * as schema from './getSchema';
|
||||
|
||||
const schemaObjects = ['model', 'view', 'type'] as const;
|
||||
|
||||
export function isOneOfSchemaObjects<T extends string>(
|
||||
obj: schema.Object,
|
||||
schemas: readonly T[]
|
||||
): obj is Extract<schema.Object, { type: T }> {
|
||||
return obj != null && 'type' in obj && schemas.includes(obj.type as T);
|
||||
}
|
||||
|
||||
/** Returns true if the value is an Object, such as a model or view or composite type. */
|
||||
export function isSchemaObject(
|
||||
obj: schema.Object
|
||||
): obj is Extract<schema.Object, { type: (typeof schemaObjects)[number] }> {
|
||||
return isOneOfSchemaObjects(obj, schemaObjects);
|
||||
}
|
||||
|
||||
const fieldObjects = ['field', 'enumerator'] as const;
|
||||
/** Returns true if the value is a Field or Enumerator. */
|
||||
export function isSchemaField(
|
||||
field: schema.Field | schema.Enumerator
|
||||
): field is Extract<schema.Field, { type: (typeof fieldObjects)[number] }> {
|
||||
return field != null && 'type' in field && fieldObjects.includes(field.type);
|
||||
}
|
||||
|
||||
/** Returns true if the value of the CstNode is a Token. */
|
||||
export function isToken(node: [IToken] | [CstNode]): node is [IToken] {
|
||||
return 'image' in node[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* If parser.nodeLocationTracking is set, then read the location statistics
|
||||
* from the available tokens. If tracking is 'none' then just return the
|
||||
* existing data structure.
|
||||
* */
|
||||
export function appendLocationData<T extends Record<string, unknown>>(
|
||||
data: T,
|
||||
...tokens: IToken[]
|
||||
): T {
|
||||
const location = tokens.reduce((memo, token) => {
|
||||
if (!token) return memo;
|
||||
|
||||
const {
|
||||
endColumn = -Infinity,
|
||||
endLine = -Infinity,
|
||||
endOffset = -Infinity,
|
||||
startColumn = Infinity,
|
||||
startLine = Infinity,
|
||||
startOffset = Infinity,
|
||||
} = memo;
|
||||
|
||||
if (token.startLine != null && token.startLine < startLine)
|
||||
memo.startLine = token.startLine;
|
||||
if (token.startColumn != null && token.startColumn < startColumn)
|
||||
memo.startColumn = token.startColumn;
|
||||
if (token.startOffset != null && token.startOffset < startOffset)
|
||||
memo.startOffset = token.startOffset;
|
||||
|
||||
if (token.endLine != null && token.endLine > endLine)
|
||||
memo.endLine = token.endLine;
|
||||
if (token.endColumn != null && token.endColumn > endColumn)
|
||||
memo.endColumn = token.endColumn;
|
||||
if (token.endOffset != null && token.endOffset > endOffset)
|
||||
memo.endOffset = token.endOffset;
|
||||
|
||||
return memo;
|
||||
}, {} as IToken);
|
||||
|
||||
return Object.assign(data, { location });
|
||||
}
|
||||
292
_node_modules/@mrleebo/prisma-ast/src/visitor.ts
generated
Normal file
292
_node_modules/@mrleebo/prisma-ast/src/visitor.ts
generated
Normal file
@@ -0,0 +1,292 @@
|
||||
import { CstNode, IToken } from '@chevrotain/types';
|
||||
import * as Types from './getSchema';
|
||||
|
||||
import { appendLocationData, isToken } from './schemaUtils';
|
||||
import { PrismaParser, defaultParser } from './parser';
|
||||
import { ICstVisitor } from 'chevrotain';
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
type Class<T> = new (...args: any[]) => T;
|
||||
export type PrismaVisitor = ICstVisitor<any, any>;
|
||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||
|
||||
export const VisitorClassFactory = (
|
||||
parser: PrismaParser
|
||||
): Class<PrismaVisitor> => {
|
||||
const BasePrismaVisitor = parser.getBaseCstVisitorConstructorWithDefaults();
|
||||
return class PrismaVisitor extends BasePrismaVisitor {
|
||||
constructor() {
|
||||
super();
|
||||
this.validateVisitor();
|
||||
}
|
||||
|
||||
schema(ctx: CstNode & { list: CstNode[] }): Types.Schema {
|
||||
const list = ctx.list?.map((item) => this.visit([item])) || [];
|
||||
return { type: 'schema', list };
|
||||
}
|
||||
|
||||
component(
|
||||
ctx: CstNode & {
|
||||
type: [IToken];
|
||||
componentName: [IToken];
|
||||
block: [CstNode];
|
||||
}
|
||||
): Types.Block {
|
||||
const [type] = ctx.type;
|
||||
const [name] = ctx.componentName;
|
||||
const list = this.visit(ctx.block);
|
||||
|
||||
const data = (() => {
|
||||
switch (type.image) {
|
||||
case 'datasource':
|
||||
return {
|
||||
type: 'datasource',
|
||||
name: name.image,
|
||||
assignments: list,
|
||||
} as const satisfies Types.Datasource;
|
||||
case 'generator':
|
||||
return {
|
||||
type: 'generator',
|
||||
name: name.image,
|
||||
assignments: list,
|
||||
} as const satisfies Types.Generator;
|
||||
case 'model':
|
||||
return {
|
||||
type: 'model',
|
||||
name: name.image,
|
||||
properties: list,
|
||||
} as const satisfies Types.Model;
|
||||
case 'view':
|
||||
return {
|
||||
type: 'view',
|
||||
name: name.image,
|
||||
properties: list,
|
||||
} as const satisfies Types.View;
|
||||
case 'enum':
|
||||
return {
|
||||
type: 'enum',
|
||||
name: name.image,
|
||||
enumerators: list,
|
||||
} as const satisfies Types.Enum;
|
||||
case 'type':
|
||||
return {
|
||||
type: 'type',
|
||||
name: name.image,
|
||||
properties: list,
|
||||
} as const satisfies Types.Type;
|
||||
default:
|
||||
throw new Error(`Unexpected block type: ${type}`);
|
||||
}
|
||||
})();
|
||||
|
||||
return this.maybeAppendLocationData(data, type, name);
|
||||
}
|
||||
|
||||
break(): Types.Break {
|
||||
return { type: 'break' };
|
||||
}
|
||||
|
||||
comment(ctx: CstNode & { text: [IToken] }): Types.Comment {
|
||||
const [comment] = ctx.text;
|
||||
const data = {
|
||||
type: 'comment',
|
||||
text: comment.image,
|
||||
} as const satisfies Types.Comment;
|
||||
return this.maybeAppendLocationData(data, comment);
|
||||
}
|
||||
|
||||
block(ctx: CstNode & { list: CstNode[] }): BlockList {
|
||||
return ctx.list?.map((item) => this.visit([item]));
|
||||
}
|
||||
|
||||
assignment(
|
||||
ctx: CstNode & { assignmentName: [IToken]; assignmentValue: [CstNode] }
|
||||
): Types.Assignment {
|
||||
const value = this.visit(ctx.assignmentValue);
|
||||
const [key] = ctx.assignmentName;
|
||||
const data = {
|
||||
type: 'assignment',
|
||||
key: key.image,
|
||||
value,
|
||||
} as const satisfies Types.Assignment;
|
||||
return this.maybeAppendLocationData(data, key);
|
||||
}
|
||||
|
||||
field(
|
||||
ctx: CstNode & {
|
||||
fieldName: [IToken];
|
||||
fieldType: [CstNode];
|
||||
array: [IToken];
|
||||
optional: [IToken];
|
||||
attributeList: CstNode[];
|
||||
comment: [IToken];
|
||||
}
|
||||
): Types.Field {
|
||||
const fieldType = this.visit(ctx.fieldType);
|
||||
const [name] = ctx.fieldName;
|
||||
const attributes = ctx.attributeList?.map((item) => this.visit([item]));
|
||||
const comment = ctx.comment?.[0]?.image;
|
||||
const data = {
|
||||
type: 'field',
|
||||
name: name.image,
|
||||
fieldType,
|
||||
array: ctx.array != null,
|
||||
optional: ctx.optional != null,
|
||||
attributes,
|
||||
comment,
|
||||
} as const satisfies Types.Field;
|
||||
|
||||
return this.maybeAppendLocationData(
|
||||
data,
|
||||
name,
|
||||
ctx.optional?.[0],
|
||||
ctx.array?.[0]
|
||||
);
|
||||
}
|
||||
|
||||
fieldAttribute(
|
||||
ctx: CstNode & {
|
||||
fieldAttribute: [IToken];
|
||||
groupName: [IToken];
|
||||
attributeName: [IToken];
|
||||
attributeArg: CstNode[];
|
||||
}
|
||||
): Types.Attr {
|
||||
const [name] = ctx.attributeName;
|
||||
const [group] = ctx.groupName || [{}];
|
||||
const args = ctx.attributeArg?.map((attr) => this.visit(attr));
|
||||
const data = {
|
||||
type: 'attribute',
|
||||
name: name.image,
|
||||
kind: 'field',
|
||||
group: group.image,
|
||||
args,
|
||||
} as const satisfies Types.Attr;
|
||||
return this.maybeAppendLocationData(
|
||||
data,
|
||||
name,
|
||||
...ctx.fieldAttribute,
|
||||
group
|
||||
);
|
||||
}
|
||||
|
||||
blockAttribute(
|
||||
ctx: CstNode & {
|
||||
blockAttribute: [IToken];
|
||||
groupName: [IToken];
|
||||
attributeName: [IToken];
|
||||
attributeArg: CstNode[];
|
||||
}
|
||||
): Types.Attr | null {
|
||||
const [name] = ctx.attributeName;
|
||||
const [group] = ctx.groupName || [{}];
|
||||
const args = ctx.attributeArg?.map((attr) => this.visit(attr));
|
||||
const data = {
|
||||
type: 'attribute',
|
||||
name: name.image,
|
||||
kind: 'object',
|
||||
group: group.image,
|
||||
args,
|
||||
} as const satisfies Types.Attr;
|
||||
|
||||
return this.maybeAppendLocationData(
|
||||
data,
|
||||
name,
|
||||
...ctx.blockAttribute,
|
||||
group
|
||||
);
|
||||
}
|
||||
|
||||
attributeArg(ctx: CstNode & { value: [CstNode] }): Types.AttributeArgument {
|
||||
const value = this.visit(ctx.value);
|
||||
return { type: 'attributeArgument', value };
|
||||
}
|
||||
|
||||
func(
|
||||
ctx: CstNode & {
|
||||
funcName: [IToken];
|
||||
value: CstNode[];
|
||||
keyedArg: CstNode[];
|
||||
}
|
||||
): Types.Func {
|
||||
const [name] = ctx.funcName;
|
||||
const params = ctx.value?.map((item) => this.visit([item]));
|
||||
const keyedParams = ctx.keyedArg?.map((item) => this.visit([item]));
|
||||
const pars = (params || keyedParams) && [
|
||||
...(params ?? []),
|
||||
...(keyedParams ?? []),
|
||||
];
|
||||
const data = {
|
||||
type: 'function',
|
||||
name: name.image,
|
||||
params: pars,
|
||||
} as const satisfies Types.Func;
|
||||
return this.maybeAppendLocationData(data, name);
|
||||
}
|
||||
|
||||
array(ctx: CstNode & { value: CstNode[] }): Types.RelationArray {
|
||||
const args = ctx.value?.map((item) => this.visit([item]));
|
||||
return { type: 'array', args };
|
||||
}
|
||||
|
||||
keyedArg(
|
||||
ctx: CstNode & { keyName: [IToken]; value: [CstNode] }
|
||||
): Types.KeyValue {
|
||||
const [key] = ctx.keyName;
|
||||
const value = this.visit(ctx.value);
|
||||
const data = {
|
||||
type: 'keyValue',
|
||||
key: key.image,
|
||||
value,
|
||||
} as const satisfies Types.KeyValue;
|
||||
return this.maybeAppendLocationData(data, key);
|
||||
}
|
||||
|
||||
value(ctx: CstNode & { value: [IToken] | [CstNode] }): Types.Value {
|
||||
if (isToken(ctx.value)) {
|
||||
const [{ image }] = ctx.value;
|
||||
return image;
|
||||
}
|
||||
return this.visit(ctx.value);
|
||||
}
|
||||
|
||||
enum(
|
||||
ctx: CstNode & {
|
||||
enumName: [IToken];
|
||||
attributeList: CstNode[];
|
||||
comment: [IToken];
|
||||
}
|
||||
): Types.Enumerator {
|
||||
const [name] = ctx.enumName;
|
||||
const attributes = ctx.attributeList?.map((item) => this.visit([item]));
|
||||
const comment = ctx.comment?.[0]?.image;
|
||||
const data = {
|
||||
type: 'enumerator',
|
||||
name: name.image,
|
||||
attributes,
|
||||
comment,
|
||||
} as const satisfies Types.Enumerator;
|
||||
return this.maybeAppendLocationData(data, name);
|
||||
}
|
||||
|
||||
maybeAppendLocationData<T extends Record<string, unknown>>(
|
||||
data: T,
|
||||
...tokens: IToken[]
|
||||
): T {
|
||||
if (parser.config.nodeLocationTracking === 'none') return data;
|
||||
return appendLocationData(data, ...tokens);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
type BlockList = Array<
|
||||
| Types.Comment
|
||||
| Types.Property
|
||||
| Types.Attribute
|
||||
| Types.Field
|
||||
| Types.Enum
|
||||
| Types.Assignment
|
||||
| Types.Break
|
||||
>;
|
||||
export const DefaultVisitorClass = VisitorClassFactory(defaultParser);
|
||||
export const defaultVisitor = new DefaultVisitorClass();
|
||||
Reference in New Issue
Block a user