Aktueller Stand

This commit is contained in:
2026-01-23 01:33:35 +01:00
parent 082dc5e110
commit 2766dd12c5
10109 changed files with 1578841 additions and 77685 deletions

21
backend/node_modules/@mrleebo/prisma-ast/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Jeremy Liberman
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

241
backend/node_modules/@mrleebo/prisma-ast/README.md generated vendored Normal file
View File

@@ -0,0 +1,241 @@
<p align="center">
<a href="https://www.npmjs.com/package/prisma-ast" target="_blank" rel="noopener">
<img src="https://img.shields.io/npm/dw/@mrleebo/prisma-ast.svg" alt="Total Downloads" />
</a>
<a href="https://www.npmjs.com/package/@mrleebo/prisma-ast" target="_blank" rel="noopener">
<img src="https://img.shields.io/npm/v/@mrleebo/prisma-ast.svg" alt="npm package"/>
</a>
<a href="https://github.com/mrleebo/prisma-ast/blob/main/LICENSE" target="_blank" rel="noopener">
<img src="https://img.shields.io/npm/l/@mrleebo/prisma-ast.svg" alt="License">
</a>
</p>
<p align="center">
<a href="https://www.prisma.io/">
<img src="https://img.shields.io/badge/Prisma-3982CE?style=for-the-badge&logo=Prisma&logoColor=white" height="28" />
</a>
<a href="https://www.buymeacoffee.com/mrleebo" target="_blank">
<img src="https://cdn.buymeacoffee.com/buttons/v2/default-violet.png" alt="Buy Me A Coffee" height="28" >
</a>
</p>
# @mrleebo/prisma-ast
This library uses an abstract syntax tree to parse schema.prisma files into an object in JavaScript. It also allows you to update your Prisma schema files using a Builder object pattern that is fully implemented in TypeScript.
It is similar to [@prisma/sdk](https://github.com/prisma/prisma/tree/master/src/packages/sdk) except that it preserves comments and model attributes. It also doesn't attempt to validate the correctness of the schema at all; the focus is instead on the ability to parse the schema into an object, manipulate it using JavaScript, and re-print the schema back to a file without losing information that isn't captured by other parsers.
> It is probable that a future version of @prisma/sdk will render this library obsolete.
## Install
```bash
npm install @mrleebo/prisma-ast
```
## Examples
### Produce a modified schema by building upon an existing schema
```ts
produceSchema(source: string, (builder: PrismaSchemaBuilder) => void, printOptions?: PrintOptions): string
```
produceSchema is the simplest way to interact with prisma-ast; you input your schema source and a producer function to produce modifications to it, and it will output the schema source with your modifications applied.
```ts
import { produceSchema } from '@mrleebo/prisma-ast';
const source = `
model User {
id Int @id @default(autoincrement())
name String @unique
}
`;
const output = produceSchema(source, (builder) => {
builder
.model('AppSetting')
.field('key', 'String', [{ name: 'id' }])
.field('value', 'Json');
});
```
```prisma
model User {
id Int @id @default(autoincrement())
name String @unique
}
model AppSetting {
key String @id
value Json
}
```
For more information about what the builder can do, check out the [PrismaSchemaBuilder](#prismaschemabuilder) class.
### PrismaSchemaBuilder
The `produceSchema()` utility will construct a builder for you, but you can also create your own instance, which may be useful for more interactive use-cases.
```ts
import { createPrismaSchemaBuilder } from '@mrleebo/prisma-ast';
const builder = createPrismaSchemaBuilder();
builder
.model('User')
.field('id', 'Int')
.attribute('id')
.attribute('default', [{ name: 'autoincrement' }])
.field('name', 'String')
.attribute('unique')
.break()
.comment('this is a comment')
.blockAttribute('index', ['name']);
const output = builder.print();
```
```prisma
model User {
id Int @id @default(autoincrement())
name String @unique
// this is a comment
@@index([name])
}
```
### Query the prisma schema for specific objects
The builder can also help you find matching objects in the schema based on name (by string or RegExp) or parent context. You can use this to write tests against your schema, or find fields that don't match a naming convention, for example.
```ts
const source = `
model Product {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String
photos Photo[]
}
`
const builder = createPrismaSchemaBuilder(source);
const product = builder.findByType('model', { name: 'Product' });
expect(product).toHaveProperty('name', 'Product');
const id = builder.findByType('field', {
name: 'id',
within: product?.properties,
});
expect(id).toHaveProperty('name', 'id');
const map = builder.findByType('attribute', {
name: 'map',
within: id?.attributes,
});
expect(map).toHaveProperty('name', 'map');
```
### Re-sort the schema
prisma-ast can sort the schema for you. The default sort order is `['generator', 'datasource', 'model', 'enum']` and will sort objects of the same type alphabetically.
```ts
print(options?: {
sort: boolean,
locales?: string | string[],
sortOrder?: Array<'generator' | 'datasource' | 'model' | 'enum'>
})
```
You can optionally set your own sort order, or change the locale used by the sort.
```ts
// sort with default parameters
builder.print({ sort: true });
// sort with options
builder.print({
sort: true,
locales: 'en-US',
sortOrder: ['datasource', 'generator', 'model', 'enum'],
});
```
### Need More SchemaBuilder Code snippets?
There is a lot that you can do with the schema builder. There are [additional sample references available](./EXAMPLES.md) for you to explore.
## Configuration Options
prisma-ast uses [lilconfig](https://github.com/antonk52/lilconfig) to read configuration options which
can be located in any of the following files, and in several other variations (see [the complete list of search paths](https://www.npmjs.com/package/cosmiconfig)):
- `"prisma-ast"` in `package.json`
- `.prisma-astrc`
- `.prisma-astrc.json`
- `.prisma-astrc.js`
- `.config/.prisma-astrc`
Configuration options are:
| Option | Description | Default Value |
| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
| `parser.nodeTrackingLocation` | Include the token locations of CST Nodes in the output schema.<br>Disabled by default because it can impact parsing performance.<br>Possible values are `"none"`, `"onlyOffset"`, and `"full"`. | `"none"` |
### Example Custom Configuration
Here is an example of how you can customize your configuration options in `package.json`.
```json
{
"prisma-ast": {
"parser": {
"nodeTrackingLocation": "full"
}
}
}
```
## Underlying utility functions
The `produceSchema` and `createPrismaSchemaBuilder` functions are intended to be your interface for interacting with the prisma schema, but you can also get direct access to the AST representation if you need to edit the schema for more advanced usages that aren't covered by the methods above.
### Parse a schema.prisma file into an AST object
The shape of the AST is not fully documented, and it is more likely to change than the builder API.
```ts
import { getSchema } from '@mrleebo/prisma-ast';
const source = `
model User {
id Int @id @default(autoincrement())
name String @unique
}
`;
const schema = getSchema(source);
```
### Print a schema AST back out as a string
This is what `builder.print()` calls internally, and is what you'd use to print if you called `getSchema()`.
```ts
import { printSchema } from '@mrleebo/prisma-ast';
const source = printSchema(schema);
```
You can optionally re-sort the schema. The default sort order is `['generator', 'datasource', 'model', 'enum']`, and objects with the same type are sorted alphabetically, but the sort order can be overridden.
```ts
const source = printSchema(schema, {
sort: true,
locales: 'en-US',
sortOrder: ['datasource', 'generator', 'model', 'enum'],
});
```

View File

@@ -0,0 +1,57 @@
import * as schema from './getSchema';
import { PrintOptions } from './printSchema';
import * as finder from './finder';
type ReplaceReturnType<Original extends (...args: any) => any, NewReturn> = (...a: Parameters<Original>) => NewReturn;
type ExtractKeys = 'getSchema' | 'getSubject' | 'getParent' | 'print';
type NeutralKeys = 'break' | 'comment' | 'attribute' | 'enumerator' | 'then' | 'findByType' | 'findAllByType';
type DatasourceOrGeneratorKeys = 'assignment';
type EnumKeys = 'enumerator';
type FieldKeys = 'attribute' | 'removeAttribute';
type BlockKeys = 'blockAttribute' | 'field' | 'removeField';
type PrismaSchemaFinderOptions = finder.ByTypeOptions & {
within?: finder.ByTypeSourceObject[];
};
type PrismaSchemaSubset<Universe extends keyof ConcretePrismaSchemaBuilder, Method> = ReplaceReturnType<ConcretePrismaSchemaBuilder[Universe], PrismaSchemaBuilder<Exclude<keyof ConcretePrismaSchemaBuilder, Method>>>;
type PrismaSchemaBuilder<K extends keyof ConcretePrismaSchemaBuilder> = {
[U in K]: U extends ExtractKeys ? ConcretePrismaSchemaBuilder[U] : U extends NeutralKeys ? ConcretePrismaSchemaBuilder[U] : 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 Subject = schema.Block | schema.Field | schema.Enumerator | undefined;
export declare class ConcretePrismaSchemaBuilder {
private schema;
private _subject;
private _parent;
constructor(source?: string);
print(options?: PrintOptions): string;
getSchema(): schema.Schema;
generator(name: string, provider?: string): this;
drop(name: string): this;
datasource(provider: string, url: string | {
env: string;
}): this;
model(name: string): this;
view(name: string): this;
type(name: string): this;
enum(name: string, enumeratorNames?: string[]): this;
enumerator(value: string): this;
private getSubject;
private getParent;
blockAttribute(name: string, args?: string | string[] | Record<string, schema.Value>): this;
attribute<T extends schema.Field>(name: string, args?: Arg[] | Record<string, string[]>): this;
removeAttribute<T extends schema.Field>(name: string): this;
assignment<T extends schema.Generator | schema.Datasource>(key: string, value: string): this;
findByType<const Match extends finder.ByTypeMatch>(typeToMatch: Match, { within, ...options }: PrismaSchemaFinderOptions): finder.FindByBlock<Match> | null;
findAllByType<const Match extends finder.ByTypeMatch>(typeToMatch: Match, { within, ...options }: PrismaSchemaFinderOptions): Array<finder.FindByBlock<Match> | null>;
private blockInsert;
break(): this;
comment(text: string, node?: boolean): this;
schemaComment(text: string, node?: boolean): this;
field(name: string, fieldType?: string | schema.Func): this;
removeField(name: string): this;
then<R extends NonNullable<Subject>>(callback: (subject: R) => unknown): this;
}
export declare function createPrismaSchemaBuilder(source?: string): PrismaSchemaBuilder<Exclude<keyof ConcretePrismaSchemaBuilder, DatasourceOrGeneratorKeys | EnumKeys | FieldKeys | BlockKeys>>;
export {};

View File

@@ -0,0 +1,12 @@
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 declare const findByType: <const Match extends "model" | "view" | "datasource" | "generator" | "enum" | "type" | "enumerator" | "field" | "attribute" | "assignment">(list: ByTypeSourceObject[], typeToMatch: Match, options?: ByTypeOptions) => FindByBlock<Match> | null;
export declare const findAllByType: <const Match extends "model" | "view" | "datasource" | "generator" | "enum" | "type" | "enumerator" | "field" | "attribute" | "assignment">(list: ByTypeSourceObject[], typeToMatch: Match, options?: ByTypeOptions) => FindByBlock<Match>[];

View File

@@ -0,0 +1,6 @@
import type { IParserConfig } from 'chevrotain';
export type PrismaAstParserConfig = Pick<IParserConfig, 'nodeLocationTracking'>;
export interface PrismaAstConfig {
parser: PrismaAstParserConfig;
}
export default function getConfig(): PrismaAstConfig;

View File

@@ -0,0 +1,119 @@
import { PrismaVisitor } from './visitor';
import type { CstNodeLocation } from 'chevrotain';
import { PrismaParser } from './parser';
export declare function getSchema(source: string, options?: {
parser: PrismaParser;
visitor: PrismaVisitor;
}): Schema;
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>;

View 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';

View File

@@ -0,0 +1,8 @@
'use strict'
if (process.env.NODE_ENV === 'production') {
module.exports = require('./prisma-ast.cjs.production.min.js')
} else {
module.exports = require('./prisma-ast.cjs.development.js')
}

View File

@@ -0,0 +1,34 @@
import { Lexer, IMultiModeLexerDefinition } from 'chevrotain';
export declare const Identifier: import("chevrotain").TokenType;
export declare const Datasource: import("chevrotain").TokenType;
export declare const Generator: import("chevrotain").TokenType;
export declare const Model: import("chevrotain").TokenType;
export declare const View: import("chevrotain").TokenType;
export declare const Enum: import("chevrotain").TokenType;
export declare const Type: import("chevrotain").TokenType;
export declare const True: import("chevrotain").TokenType;
export declare const False: import("chevrotain").TokenType;
export declare const Null: import("chevrotain").TokenType;
export declare const Comment: import("chevrotain").TokenType;
export declare const DocComment: import("chevrotain").TokenType;
export declare const LineComment: import("chevrotain").TokenType;
export declare const Attribute: import("chevrotain").TokenType;
export declare const BlockAttribute: import("chevrotain").TokenType;
export declare const FieldAttribute: import("chevrotain").TokenType;
export declare const Dot: import("chevrotain").TokenType;
export declare const QuestionMark: import("chevrotain").TokenType;
export declare const LCurly: import("chevrotain").TokenType;
export declare const RCurly: import("chevrotain").TokenType;
export declare const LRound: import("chevrotain").TokenType;
export declare const RRound: import("chevrotain").TokenType;
export declare const LSquare: import("chevrotain").TokenType;
export declare const RSquare: import("chevrotain").TokenType;
export declare const Comma: import("chevrotain").TokenType;
export declare const Colon: import("chevrotain").TokenType;
export declare const Equals: import("chevrotain").TokenType;
export declare const StringLiteral: import("chevrotain").TokenType;
export declare const NumberLiteral: import("chevrotain").TokenType;
export declare const WhiteSpace: import("chevrotain").TokenType;
export declare const LineBreak: import("chevrotain").TokenType;
export declare const multiModeTokens: IMultiModeLexerDefinition;
export declare const PrismaLexer: Lexer;

View File

@@ -0,0 +1,23 @@
import { CstParser } from 'chevrotain';
import { PrismaAstParserConfig } from './getConfig';
export declare class PrismaParser extends CstParser {
readonly config: PrismaAstParserConfig;
constructor(config: PrismaAstParserConfig);
private break;
private keyedArg;
private array;
private func;
private value;
private property;
private assignment;
private field;
private block;
private enum;
private fieldAttribute;
private blockAttribute;
private attributeArg;
private component;
private comment;
schema: import("chevrotain").ParserMethod<[], import("chevrotain").CstNode>;
}
export declare const defaultParser: PrismaParser;

View File

@@ -0,0 +1,9 @@
import * as Types from './getSchema';
type Block = 'generator' | 'datasource' | 'model' | 'view' | 'enum' | 'type';
export interface PrintOptions {
sort?: boolean;
locales?: string | string[];
sortOrder?: Block[];
}
export declare function printSchema(schema: Types.Schema, options?: PrintOptions): string;
export {};

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,5 @@
import { PrintOptions } from './printSchema';
import { createPrismaSchemaBuilder } from './PrismaSchemaBuilder';
type Options = PrintOptions;
export declare function produceSchema(source: string, producer: (builder: ReturnType<typeof createPrismaSchemaBuilder>) => void, options?: Options): string;
export {};

View File

@@ -0,0 +1,2 @@
import { Block, Schema } from './getSchema';
export declare const schemaSorter: (schema: Schema, locales?: string | string[], sortOrder?: string[]) => (a: Block, b: Block) => number;

View File

@@ -0,0 +1,16 @@
import type { CstNode, IToken } from 'chevrotain';
import * as schema from './getSchema';
declare const schemaObjects: readonly ["model", "view", "type"];
export declare function isOneOfSchemaObjects<T extends string>(obj: schema.Object, schemas: readonly T[]): obj is Extract<schema.Object, {
type: T;
}>;
export declare function isSchemaObject(obj: schema.Object): obj is Extract<schema.Object, {
type: (typeof schemaObjects)[number];
}>;
declare const fieldObjects: readonly ["field", "enumerator"];
export declare function isSchemaField(field: schema.Field | schema.Enumerator): field is Extract<schema.Field, {
type: (typeof fieldObjects)[number];
}>;
export declare function isToken(node: [IToken] | [CstNode]): node is [IToken];
export declare function appendLocationData<T extends Record<string, unknown>>(data: T, ...tokens: IToken[]): T;
export {};

View File

@@ -0,0 +1,8 @@
import { PrismaParser } from './parser';
import { ICstVisitor } from 'chevrotain';
type Class<T> = new (...args: any[]) => T;
export type PrismaVisitor = ICstVisitor<any, any>;
export declare const VisitorClassFactory: (parser: PrismaParser) => Class<PrismaVisitor>;
export declare const DefaultVisitorClass: Class<PrismaVisitor>;
export declare const defaultVisitor: PrismaVisitor;
export {};

81
backend/node_modules/@mrleebo/prisma-ast/package.json generated vendored Normal file
View File

@@ -0,0 +1,81 @@
{
"version": "0.13.1",
"license": "MIT",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"files": [
"dist",
"src"
],
"engines": {
"node": ">=16"
},
"scripts": {
"start": "dts watch",
"build": "dts build",
"test": "dts test",
"test:watch": "dts test --watch",
"lint": "eslint src",
"prepare": "dts build",
"size": "NODE_OPTIONS=--openssl-legacy-provider size-limit",
"publish-better": "npx np"
},
"peerDependencies": {},
"husky": {
"hooks": {
"pre-commit": "eslint src"
}
},
"prettier": {
"printWidth": 80,
"semi": true,
"singleQuote": true,
"trailingComma": "es5"
},
"name": "@mrleebo/prisma-ast",
"author": "Jeremy Liberman",
"module": "dist/prisma-ast.esm.js",
"size-limit": [
{
"path": "dist/prisma-ast.cjs.production.min.js",
"limit": "56 KB"
},
{
"path": "dist/prisma-ast.esm.js",
"limit": "56 KB"
}
],
"devDependencies": {
"@size-limit/preset-small-lib": "^8.2.6",
"@typescript-eslint/eslint-plugin": "^4.22.0",
"@typescript-eslint/parser": "^4.22.0",
"dts-cli": "^2.0.3",
"eslint": "^7.24.0",
"husky": "^6.0.0",
"jest": "^29.6.0",
"prettier": "^2.8.8",
"size-limit": "^8.2.6",
"ts-jest": "^29.1.1",
"tslib": "^2.4.0",
"typescript": "^5.1.6"
},
"dependencies": {
"chevrotain": "^10.5.0",
"lilconfig": "^2.1.0"
},
"publishConfig": {
"access": "public"
},
"description": "This library uses an abstract syntax tree to parse schema.prisma files into an object in JavaScript. It is similar to [@prisma/sdk](https://github.com/prisma/prisma/tree/master/src/packages/sdk) except that it preserves comments and model attributes.",
"directories": {
"test": "test"
},
"repository": {
"type": "git",
"url": "git+https://github.com/MrLeebo/prisma-ast.git"
},
"bugs": {
"url": "https://github.com/MrLeebo/prisma-ast/issues"
},
"homepage": "https://github.com/MrLeebo/prisma-ast#readme"
}

View 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
backend/node_modules/@mrleebo/prisma-ast/src/finder.ts generated vendored Normal file
View 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;
};

View 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));
}

View 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>;

View 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
backend/node_modules/@mrleebo/prisma-ast/src/lexer.ts generated vendored Normal file
View 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
backend/node_modules/@mrleebo/prisma-ast/src/parser.ts generated vendored Normal file
View 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);

View 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} `);
}

View 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);
}

View 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;
};

View 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
backend/node_modules/@mrleebo/prisma-ast/src/visitor.ts generated vendored Normal file
View 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();