Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: schema to types #463

Draft
wants to merge 9 commits into
base: dev
Choose a base branch
from
8 changes: 8 additions & 0 deletions src/components/Data/TypeLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { IDisposable } from '/@/types/disposable'
import { DataLoader } from './DataLoader'
import { Tab } from '../TabSystem/CommonTab'
import { FileTab } from '../TabSystem/FileTab'
import { toTypeDefinition } from '../JSONSchema/ToTypes/main'
import {
IRequirements,
RequiresMatcher,
Expand Down Expand Up @@ -69,6 +70,13 @@ export class TypeLoader {
await App.fileType.ready.fired
const { types = [] } = App.fileType.get(filePath) ?? {}

// console.log(
// ...toTypeDefinition(
// App.fileType.all.filter(
// (fileType) => fileType.id === 'lootTable'
// )[0]?.schema
// )
// )
const matcher = new RequiresMatcher()
await matcher.setup()

Expand Down
1 change: 1 addition & 0 deletions src/components/JSONSchema/Schema/AllOf.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ParentSchema } from './Parent'
import { RootSchema } from './Root'
import { Schema } from './Schema'
import { UnionType } from '../ToTypes/Union'

export class AllOfSchema extends ParentSchema {
protected children: Schema[]
Expand Down
5 changes: 5 additions & 0 deletions src/components/JSONSchema/Schema/Const.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { LiteralType } from '../ToTypes/Literal'
import { Schema } from './Schema'

export class ConstSchema extends Schema {
Expand All @@ -21,4 +22,8 @@ export class ConstSchema extends Schema {
]
return []
}

override toTypeDefinition() {
return new LiteralType(<string | number | boolean>this.value)
}
}
4 changes: 4 additions & 0 deletions src/components/JSONSchema/Schema/ElseSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,8 @@ export class ElseSchema extends Schema {
if (!this.ifSchema.isTrue(obj)) return this.rootSchema.validate(obj)
return []
}

override toTypeDefinition(hoisted: Set<Schema>) {
return this.rootSchema.toTypeDefinition(hoisted)
}
}
8 changes: 8 additions & 0 deletions src/components/JSONSchema/Schema/Enum.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { LiteralType } from '../ToTypes/Literal'
import { UnionType } from '../ToTypes/Union'
import { Schema } from './Schema'

export class EnumSchema extends Schema {
Expand Down Expand Up @@ -31,4 +33,10 @@ export class EnumSchema extends Schema {
]
return []
}

override toTypeDefinition() {
if(!Array.isArray(this.value)) return null

return new UnionType(this.value.map(val => new LiteralType(val)))
}
}
21 changes: 21 additions & 0 deletions src/components/JSONSchema/Schema/Items.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { RootSchema } from './Root'
import { IDiagnostic, Schema } from './Schema'
import { TupleType } from '../ToTypes/Tuple'
import { ArrayType } from '../ToTypes/Array'
import { BaseType } from '../ToTypes/Type'

export class ItemsSchema extends Schema {
protected children: RootSchema | RootSchema[]
Expand Down Expand Up @@ -70,4 +73,22 @@ export class ItemsSchema extends Schema {
validate(obj: unknown) {
return []
}

override toTypeDefinition(hoisted: Set<Schema>) {
if (Array.isArray(this.children)) {
return new TupleType(
<BaseType[]>this.children
.filter((child) => !child.hasDoNotSuggest)
.map((child) => child.toTypeDefinition(hoisted))
.filter((type) => type !== null)
)
} else {
if(this.children.hasDoNotSuggest) return null

const type = this.children.toTypeDefinition(hoisted)
if (type === null) return null

return new ArrayType(type)
}
}
}
12 changes: 12 additions & 0 deletions src/components/JSONSchema/Schema/Parent.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { BaseType } from '../ToTypes/Type'
import { UnionType } from '../ToTypes/Union'
import { DoNotSuggestSchema } from './DoNotSuggest'
import { Schema } from './Schema'

Expand Down Expand Up @@ -39,4 +41,14 @@ export abstract class ParentSchema extends Schema {

return children
}

override toTypeDefinition(hoisted: Set<Schema>) {
if(this.hasDoNotSuggest) console.warn(`[${this.location}] Called Schema.toTypeDefinition on a schema which has "doNotSuggest"`)

return new UnionType(
<BaseType[]>(this.children
.map((child) => child.toTypeDefinition(hoisted))
.filter((schema) => schema !== null))
)
}
}
16 changes: 16 additions & 0 deletions src/components/JSONSchema/Schema/Properties.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { RootSchema } from './Root'
import { InterfaceType } from '../ToTypes/Interface'
import { ICompletionItem, IDiagnostic, Schema } from './Schema'

export class PropertiesSchema extends Schema {
Expand Down Expand Up @@ -100,4 +101,19 @@ export class PropertiesSchema extends Schema {

return diagnostics
}

override toTypeDefinition(hoisted: Set<Schema>) {
const interfaceType = new InterfaceType()

for (const [propertyName, child] of Object.entries(this.children)) {
if (child.hasDoNotSuggest) continue

const type = child.toTypeDefinition(hoisted)
if (type === null) continue

interfaceType.addProperty(propertyName, type)
}

return interfaceType
}
}
22 changes: 21 additions & 1 deletion src/components/JSONSchema/Schema/Ref.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { SchemaManager } from '../Manager'
import { pathToName } from '../pathToName'
import { PrimitiveType } from '../ToTypes/Primitive'
import { RootSchema } from './Root'
import { Schema } from './Schema'
import { dirname, join } from '/@/utils/path'
import { dirname, join, relative } from '/@/utils/path'

export class RefSchema extends Schema {
public readonly schemaType = 'refSchema'
Expand Down Expand Up @@ -74,4 +76,22 @@ export class RefSchema extends Schema {
getFreeIfSchema() {
return this.rootSchema.getFreeIfSchema()
}

getName() {
return pathToName(
relative(
'file:///data/packages/minecraftBedrock/schema',
this.rootSchema.getLocation()
)
)
}

override toTypeDefinition(hoisted: Set<Schema>, forceEval?: boolean) {
if(forceEval) return this.rootSchema.toTypeDefinition(hoisted)

if(!hoisted.has(this))
hoisted.add(this)

return new PrimitiveType(this.getName())
}
}
22 changes: 22 additions & 0 deletions src/components/JSONSchema/Schema/Schema.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import { pathToName } from '../pathToName'
import { BaseType } from '../ToTypes/Type'
import { relative } from '/@/utils/path'

export interface ISchemaResult {
diagnostics: IDiagnostic[]
}
Expand Down Expand Up @@ -51,4 +55,22 @@ export abstract class Schema {
obj: unknown,
location: (string | number | undefined)[]
): Schema[]

toTypeDefinition(
hoisted: Set<Schema>,
forceEval?: boolean
): BaseType | null {
return null
}
getName() {
return pathToName(
relative(
'file:///data/packages/minecraftBedrock/schema',
this.location
)
)
}
getLocation() {
return this.location
}
}
4 changes: 4 additions & 0 deletions src/components/JSONSchema/Schema/ThenSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,8 @@ export class ThenSchema extends Schema {
if (this.ifSchema.isTrue(obj)) return this.rootSchema.validate(obj)
return []
}

override toTypeDefinition(hoisted: Set<Schema>) {
return this.rootSchema.toTypeDefinition(hoisted)
}
}
14 changes: 14 additions & 0 deletions src/components/JSONSchema/Schema/Type.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { ICompletionItem, Schema } from './Schema'
import { getTypeOf } from '/@/utils/typeof'
import { PrimitiveType, TSupportedPrimitiveTypes } from '../ToTypes/Primitive'
import { UnionType } from '../ToTypes/Union'

export class TypeSchema extends Schema {
get values() {
Expand Down Expand Up @@ -60,4 +62,16 @@ export class TypeSchema extends Schema {

return []
}

override toTypeDefinition() {
const values = (
Array.isArray(this.value) ? this.value : [this.value]
).filter((type) => !['object', 'array'].includes(type))

return new UnionType(
values.map(
(val) => new PrimitiveType(PrimitiveType.toTypeScriptType(val))
)
)
}
}
11 changes: 11 additions & 0 deletions src/components/JSONSchema/ToTypes/Array.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { BaseType } from './Type'

export class ArrayType extends BaseType {
constructor(public readonly type: BaseType) {
super()
}

public toString() {
return `${this.type.toString()}[]`
}
}
71 changes: 71 additions & 0 deletions src/components/JSONSchema/ToTypes/CombinedType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { InterfaceType } from './Interface'
import { BaseType } from './Type'

export abstract class CombinedType extends BaseType {
constructor(private types: BaseType[], protected joinChar: string) {
super()
}

protected abstract isOfThisType(type: CombinedType): boolean

add(baseType: BaseType) {
this.types.push(baseType)
}

protected flatten(): BaseType[] {
return this.types
.map((type) => {
if (type instanceof CombinedType) {
const flat = type.flatten()

if (flat.length === 0) return []
else if (flat.length === 1) return flat[0]
else if (this.isOfThisType(type)) return flat

return type
} else return type
})
.flat(1)
}

protected withCollapsedInterfaces() {
const types = this.flatten()
const interfaceType = new InterfaceType()

const newTypes: BaseType[] = []
let foundInterface = false
for (const type of types) {
if (type instanceof InterfaceType) {
interfaceType.addFrom(type)
foundInterface = true
} else {
newTypes.push(type)
}
}

return foundInterface ? newTypes.concat(interfaceType) : newTypes
}

isStringCollection(collection: string[]) {
return collection.every(
(type) =>
(type.startsWith("'") && type.endsWith("'")) ||
type === 'string'
)
}

toString() {
let collection = [
...new Set(
this.withCollapsedInterfaces().map((type) => type.toString())
),
]

if (this.isStringCollection(collection))
collection = collection.filter((type) => type !== 'string')

if (collection.length === 1) return collection[0]

return `(${collection.join(this.joinChar)})`
}
}
33 changes: 33 additions & 0 deletions src/components/JSONSchema/ToTypes/Interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { CombinedType } from './CombinedType'
import { BaseType } from './Type'
import { UnionType } from './Union'

export class InterfaceType extends BaseType {
protected properties: Record<string, BaseType> = {}

addProperty(name: string, type: BaseType) {
this.properties[name] = type
}
addFrom(interfaceType: InterfaceType) {
for (const [key, type] of Object.entries(interfaceType.properties)) {
const existingType = this.properties[key]
if(existingType instanceof CombinedType) {
existingType.add(type)
} else if(existingType) {
this.addProperty(key, new UnionType([this.properties[key], type]))
} else {
this.addProperty(key, type)
}
}
}

toString() {
return `{ ${Object.entries(this.properties)
.map(([key, type]) => `${key}?: ${type.toString()}`)
.join(';')};[k: string]: unknown; }`
}

override withName(name: string) {
return `interface ${name} ${this.toString()};`
}
}
12 changes: 12 additions & 0 deletions src/components/JSONSchema/ToTypes/Intersection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { BaseType } from './Type'
import { CombinedType } from './CombinedType'

export class IntersectionType extends CombinedType {
constructor(types: BaseType[]) {
super(types, ' & ')
}

isOfThisType(type: CombinedType) {
return type instanceof IntersectionType
}
}
12 changes: 12 additions & 0 deletions src/components/JSONSchema/ToTypes/Literal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { BaseType } from './Type'

export class LiteralType extends BaseType {
constructor(protected literalType: string | number | boolean) {
super()
}

public toString() {
if (typeof this.literalType === 'string') return `'${this.literalType}'`
return `${this.literalType}`
}
}
Loading