Skip to content

Commit

Permalink
fix(Language bindings): Flag a property if it is an override
Browse files Browse the repository at this point in the history
This addresses the "double up" described here stencila/schema#97 (comment)
  • Loading branch information
nokome committed Jul 23, 2019
1 parent 307fe45 commit 6bb1ec5
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 47 deletions.
56 changes: 26 additions & 30 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"@types/fs-extra": "^8.0.0",
"@types/jest": "^24.0.15",
"@types/js-yaml": "^3.12.1",
"@types/lodash.clonedeep": "^4.5.6",
"@types/toposort": "^2.0.1",
"ajv": "^6.10.2",
"better-ajv-errors": "^0.6.4",
Expand All @@ -50,6 +51,7 @@
"js-yaml": "^3.13.1",
"json-schema": "^0.2.3",
"json-schema-to-typescript": "^6.1.3",
"lodash.clonedeep": "^4.5.0",
"markdown-toc": "^1.2.0",
"object.fromentries": "^2.0.0",
"tempy": "^0.3.0",
Expand Down
7 changes: 4 additions & 3 deletions src/bindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ interface Property {
name: string
schema: Schema
inherited: boolean
override: boolean
optional: boolean
}

Expand All @@ -87,10 +88,10 @@ export function props(
.filter(([name, _]) => name !== 'type')
.map(
([name, schema]): Property => {
const { from } = schema
const { from, override = false } = schema
const inherited = from !== title
const optional = required === undefined || !required.includes(name)
return { name, schema, inherited, optional }
return { name, schema, inherited, override, optional }
}
)
.sort((a, b) => {
Expand All @@ -106,7 +107,7 @@ export function props(
return {
all: props,
inherited: props.filter(prop => prop.inherited),
own: props.filter(prop => !prop.inherited || !prop.optional),
own: props.filter(prop => !prop.inherited || prop.override),
required: props.filter(prop => !prop.optional),
optional: props.filter(prop => prop.optional)
}
Expand Down
7 changes: 7 additions & 0 deletions src/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ export default interface Schema extends JSONSchema7 {
*/
from?: string

/**
* Is this property schema an override of a property inherited
* from an ancestor. Examples of overrides include making an
* optional property required, or changing the schema of the property.
*/
override?: boolean

/**
* Aliases for this property schema.
* Only applies when used in a property of another schema.
Expand Down
44 changes: 30 additions & 14 deletions src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import fs from 'fs-extra'
import globby from 'globby'
import yaml from 'js-yaml'
import path from 'path'
import cloneDeep from 'lodash.clonedeep'
import Schema from './schema.d'

const SCHEMA_SOURCE_DIR = path.join(__dirname, '..', 'schema')
Expand Down Expand Up @@ -77,52 +78,67 @@ const processSchema = (schemas: Map<string, Schema>, schema: Schema): void => {
schema.source = `https://github.com/stencila/schema/blob/master/schema/${file}`

try {
const parent = parentSchema(schemas, schema)
let parentProperties: { [key: string]: Schema } = {}
let parentRequired: string[] = []
if (parent !== null) {
// Ensure that the parent schema has been processed (to collect properties)
processSchema(schemas, parent)
if (parent.properties !== undefined) parentProperties = parent.properties
if (parent.required !== undefined) parentRequired = parent.required
}

// Process properties
if (schema.properties !== undefined) {
schema.type = 'object'

const propertyAliases: { [key: string]: string } = {}
for (const [name, property] of Object.entries(schema.properties)) {
schema.properties[name].from = title

// Registered declared aliases
if (property.aliases !== undefined) {
for (const alias of property.aliases) propertyAliases[alias] = name
}
// Add aliases for array properties (if not ye registered)
// Add aliases for array properties (if not yet registered)
if (property.type === 'array' && name.endsWith('s')) {
const alias = name.slice(0, -1)
if (property.aliases === undefined) property.aliases = []
if (!property.aliases.includes(alias)) property.aliases.push(alias)
propertyAliases[alias] = name
}
// Is this an override of a property schema in parent?
if (name in parentProperties)
property.override = true
}
if (Object.keys(propertyAliases).length > 0) {

if (Object.keys(propertyAliases).length > 0)
schema.propertyAliases = propertyAliases
}

if (schema.additionalProperties === undefined) {
if (schema.additionalProperties === undefined)
schema.additionalProperties = false
}
}

// Apply `extends` keyword
const parent = parentSchema(schemas, schema)
if (parent !== null) {
// Ensure that the base schema has been processed (to collect properties)
processSchema(schemas, parent)

// Extends properties and requireds
// Extend `properties`
schema.properties = {
...(parent.properties !== undefined ? parent.properties : {}),
...cloneDeep(parentProperties),
...(schema.properties !== undefined ? schema.properties : {})
}

// Flag inherited, but newly required properties, as overrides
for (const [name, property] of Object.entries(schema.properties)) {
if (property.from !== title && schema.required !== undefined && schema.required.includes(name))
property.override = true
}

// Having done that, now we can extend `required`
schema.required = [
...(parent.required !== undefined ? parent.required : []),
...parentRequired,
...(schema.required !== undefined ? schema.required : [])
]

// Initialise the `type` property
// Initialize the `type` property
if (schema.properties.type !== undefined) {
schema.properties.type = {
...schema.properties.type,
Expand Down

0 comments on commit 6bb1ec5

Please sign in to comment.