Skip to content

Commit

Permalink
feat(Primitive types): Add schemas for primitive types
Browse files Browse the repository at this point in the history
This allows these types to be included in the list of types and have URIs generated for them.

See this for why this is needed:
https://github.com/stencila/encoda/blob/356b8e08f71880f12236bac7b0bcb2c272f4f60b/src/codecs/html/microdata.ts#L148
  • Loading branch information
nokome committed Feb 22, 2020
1 parent 274dd52 commit e402847
Show file tree
Hide file tree
Showing 14 changed files with 100 additions and 41 deletions.
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@
"lint": "npx eslint 'ts/**/*.{js,ts}' --fix",
"test": "npm run build:jsonschema && jest",
"test:cover": "npm run build:jsonschema && jest --coverage",
"build": "npm run build:jsonschema && npm run build:jsonld && npm run build:ts && npm run build:py && npm run build:r",
"build": "npm run build:jsonschema && npm run build:jsonld && npm run build:ts && npm run build:py && npm run build:r && npm run build:dist",
"build:jsonschema": "ts-node ts/schema.ts",
"build:jsonld": "ts-node ts/bindings/jsonld.ts",
"build:ts": "ts-node ts/bindings/typescript.ts && microbundle --tsconfig tsconfig.lib.json && cp public/*.schema.json dist && cp public/*.jsonld dist",
"build:ts": "ts-node ts/bindings/typescript.ts",
"build:py": "ts-node ts/bindings/python.ts",
"build:r": "ts-node ts/bindings/r.ts",
"build:dist": "microbundle --tsconfig tsconfig.lib.json && cp public/*.schema.json dist && cp public/*.jsonld dist",
"docs": "npm run docs:readme && npm run docs:build && npm run docs:api",
"docs:readme": "markdown-toc -i --maxdepth=4 README.md",
"docs:build": "ts-node ts/docs.ts",
Expand Down
4 changes: 4 additions & 0 deletions schema/Array.schema.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
title: Array
'@id': stencila:Array
description: A value comprised of several other values.
type: array
4 changes: 4 additions & 0 deletions schema/Boolean.schema.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
title: Boolean
'@id': schema:Boolean
description: A value that is either true or false
type: boolean
4 changes: 4 additions & 0 deletions schema/Null.schema.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
title: 'Null'
'@id': stencila:Null
description: The null value
type: 'null'
4 changes: 4 additions & 0 deletions schema/Number.schema.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
title: Number
'@id': schema:Number
description: A value that is a number
type: number
4 changes: 4 additions & 0 deletions schema/Object.schema.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
title: Object
'@id': stencila:Object
description: A value comprised of keyed values.
type: object
4 changes: 4 additions & 0 deletions schema/Text.schema.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
title: Text
'@id': schema:Text
description: A value comprised of a string of characters
type: string
4 changes: 2 additions & 2 deletions ts/bindings/python.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import fs from 'fs-extra'
import path from 'path'
import {
autogeneratedHeader,
filterTypeSchemas,
filterInterfaceSchemas,
filterUnionSchemas,
getSchemaProperties,
readSchemas,
Expand Down Expand Up @@ -39,7 +39,7 @@ async function build(): Promise<void> {
const schemas = await readSchemas()

globals = []
const classesCode = filterTypeSchemas(schemas)
const classesCode = filterInterfaceSchemas(schemas)
.map(classGenerator)
.join('\n\n')
const unionsCode = filterUnionSchemas(schemas)
Expand Down
4 changes: 2 additions & 2 deletions ts/bindings/r.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import fs from 'fs-extra'
import path from 'path'
import {
autogeneratedHeader,
filterTypeSchemas,
filterInterfaceSchemas,
filterUnionSchemas,
getSchemaProperties,
readSchemas,
Expand All @@ -25,7 +25,7 @@ if (module.parent === null) build()
async function build(): Promise<void> {
const schemas = await readSchemas()

const classesCode = filterTypeSchemas(schemas)
const classesCode = filterInterfaceSchemas(schemas)
.map(classGenerator)
.join('\n')
const unionsCode = filterUnionSchemas(schemas)
Expand Down
4 changes: 2 additions & 2 deletions ts/bindings/typescript.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ import * as typescript from 'typescript'
import { schema, snapshot } from '../__tests__/helpers'
import {
generateTypeDefinitions,
typeGenerator,
interfaceGenerator,
unionGenerator
} from './typescript'

test('generators', async () => {
expect(typeGenerator(await schema('Person.schema.json'))).toMatchFile(
expect(interfaceGenerator(await schema('Person.schema.json'))).toMatchFile(
snapshot(__dirname, 'Person.ts')
)

Expand Down
47 changes: 33 additions & 14 deletions ts/bindings/typescript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import path from 'path'
import prettier from 'prettier'
import {
autogeneratedHeader,
filterTypeSchemas,
filterInterfaceSchemas,
filterUnionSchemas,
getSchemaProperties,
readSchemas,
Expand All @@ -35,8 +35,8 @@ const prettify = async (contents: string): Promise<string> => {
export const generateTypeDefinitions = async (): Promise<string> => {
const schemas = await readSchemas()

const typesCode = filterTypeSchemas(schemas)
.map(typeGenerator)
const interfacesCode = filterInterfaceSchemas(schemas)
.map(interfaceGenerator)
.join('')

const unionsCode = filterUnionSchemas(schemas)
Expand All @@ -57,7 +57,7 @@ const compact = <O extends object>(o: O): O =>
${typesInterface(schemas)}
${typesCode}
${interfacesCode}
${unionsCode}
`
Expand All @@ -74,14 +74,32 @@ ${unionsCode}
*/
export const typesInterface = (schemas: Schema[]): string => {
return `export interface Types {\n${schemas
.map(({ title }) => ` ${title}: ${title}`)
.map(({ title }) => ` ${title}: ${titleToType(title ?? '')}`)
.join('\n')}\n}`
}

/**
* Convert the `title` of a JSON Schema to the name of a Typescript
* type, including the interfaces and unions generated below.
*/
export const titleToType = (title: string): string => {
switch (title) {
case 'Null':
case 'Boolean':
case 'Number':
case 'Object':
return title.toLowerCase()
case 'Array':
return 'Array<any>'
default:
return title
}
}

/**
* Generate a `interface` and a factory function for each type.
*/
export const typeGenerator = (schema: Schema): string => {
export const interfaceGenerator = (schema: Schema): string => {
const {
title = 'Undefined',
extends: parent,
Expand Down Expand Up @@ -171,7 +189,7 @@ const docComment = (description?: string, tags: string[] = []): string => {
}

/**
* Convert a schema definition to a Typescript type
* Convert a JSON Schema definition to a Typescript type
*/
const schemaToType = (schema: Schema): string => {
const { type, anyOf, allOf, $ref } = schema
Expand All @@ -193,7 +211,7 @@ const schemaToType = (schema: Schema): string => {
}

/**
* Convert a schema `$ref` (reference) to a Typescript type
* Convert a JSON Schema `$ref` (reference) to a Typescript type
*
* Assume that any `$ref`s refer to a type defined in the file.
*/
Expand All @@ -202,7 +220,7 @@ const $refToType = ($ref: string): string => {
}

/**
* Convert a schema with the `anyOf` property to a Typescript `Union` type.
* Convert a JSON Schema with the `anyOf` property to a Typescript `Union` type.
*/
const anyOfToType = (anyOf: Schema[]): string => {
const types = anyOf
Expand All @@ -217,7 +235,7 @@ const anyOfToType = (anyOf: Schema[]): string => {
}

/**
* Convert a schema with the `allOf` property to a Typescript type.
* Convert a JSON Schema with the `allOf` property to a Typescript type.
*
* If the `allOf` is singular then just use that (this usually arises
* because the `allOf` is used for a property with a `$ref`). Otherwise,
Expand All @@ -231,7 +249,7 @@ const allOfToType = (allOf: Schema[]): string => {
}

/**
* Convert a schema with the `array` property to a Typescript `Array` type.
* Convert a JSON Schema with the `array` property to a Typescript `Array` type.
*
* Uses the more explicity `Array<>` syntax over the shorter`[]` syntax
* because the latter necessitates the use of, sometime superfluous, parentheses.
Expand All @@ -246,7 +264,7 @@ const arrayToType = (schema: Schema): string => {
}

/**
* Convert a schema with the `enum` property to Typescript "or values".
* Convert a JSON Schema with the `enum` property to Typescript "or values".
*/
export const enumToType = (enu: (string | number)[]): string => {
return enu
Expand Down Expand Up @@ -305,8 +323,9 @@ export const generateTypeMaps = async (): Promise<string> => {
}

/** Generate Type Definitions and Type Maps files */
export const build = async (): Promise<unknown> => {
return generateTypeDefinitions().then(generateTypeMaps)
export const build = async (): Promise<void> => {
await generateTypeDefinitions()
await generateTypeMaps()
}

/**
Expand Down
20 changes: 17 additions & 3 deletions ts/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,27 @@ export async function readSchemas(
}

/**
* Get the 'type' schemas (i.e. not union schema, not property schemas) which are
* usually translated into classes or similar for the language.
* Is a schema for a primitive type.
*/
export function isPrimitiveSchema(schema: Schema): boolean {
return schema.properties === undefined && schema.anyOf === undefined
}

/**
* Get the 'primitive' schemas
*/
export function filterPrimitiveSchemas(schemas: Schema[]): Schema[] {
return schemas.filter(isPrimitiveSchema)
}

/**
* Get the 'interface' schemas (i.e. not union schema, not property schemas) which are
* usually translated into `interface`s, `class`es or similar for the language.
*
* Types are sorted topologically so that schemas come before
* any of their descendants.
*/
export function filterTypeSchemas(schemas: Schema[]): Schema[] {
export function filterInterfaceSchemas(schemas: Schema[]): Schema[] {
const types = schemas.filter(schema => schema.properties !== undefined)
const map = new Map(schemas.map(schema => [schema.title, schema]))

Expand Down
16 changes: 8 additions & 8 deletions ts/util/node-type.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { nodeType } from './node-type'

test('nodeType', () => {
expect(nodeType(null)).toBe('null')
expect(nodeType(true)).toBe('boolean')
expect(nodeType(false)).toBe('boolean')
expect(nodeType(42)).toBe('number')
expect(nodeType(3.14)).toBe('number')
expect(nodeType('str')).toBe('string')
expect(nodeType([])).toBe('array')
expect(nodeType({})).toBe('object')
expect(nodeType(null)).toBe('Null')
expect(nodeType(true)).toBe('Boolean')
expect(nodeType(false)).toBe('Boolean')
expect(nodeType(42)).toBe('Number')
expect(nodeType(3.14)).toBe('Number')
expect(nodeType('str')).toBe('Text')
expect(nodeType([])).toBe('Array')
expect(nodeType({})).toBe('Object')
expect(nodeType({ type: 'Person' })).toBe('Person')
})
17 changes: 9 additions & 8 deletions ts/util/node-type.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { Node } from '../types'
import { Types, Node } from '../types'
import { isEntity } from './guards'

/**
* Get the type of a node
*
* @param {Node} node The schema node to get the type for
*/
export const nodeType = (node: Node): string => {
if (node === null) return 'null'
if (typeof node === 'boolean') return 'boolean'
if (typeof node === 'number') return 'number'
if (typeof node === 'string') return 'string'
if (Array.isArray(node)) return 'array'
export const nodeType = (node: Node): keyof Types => {
if (node === null) return 'Null'
if (typeof node === 'boolean') return 'Boolean'
if (typeof node === 'number') return 'Number'
if (typeof node === 'string') return 'Text'
if (Array.isArray(node)) return 'Array'
if (isEntity(node)) return node.type
return typeof node
return 'Object'
}

0 comments on commit e402847

Please sign in to comment.