From 246b65c7228075a36b112eb3fb1d3c248e70d5bc Mon Sep 17 00:00:00 2001 From: Danielle Church Date: Thu, 25 Mar 2021 15:34:09 -0400 Subject: [PATCH 1/7] fix: Merge compatible definitions in union types Most validation keywords apply to only one of the basic types, so a "string" type and an "array" type can share a definition without colliding as a ["string", "array"] type, as long as they don't have any incompatibilities with each other. Modify UnionTypeFormatter to collapse these disjoint types into a single definition, without using anyOf. --- src/TypeFormatter/UnionTypeFormatter.ts | 13 +++++++++++++ src/Utils/mergeDefinitions.ts | 25 +++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 src/Utils/mergeDefinitions.ts diff --git a/src/TypeFormatter/UnionTypeFormatter.ts b/src/TypeFormatter/UnionTypeFormatter.ts index a8f553fea..49786aba0 100644 --- a/src/TypeFormatter/UnionTypeFormatter.ts +++ b/src/TypeFormatter/UnionTypeFormatter.ts @@ -4,6 +4,7 @@ import { SubTypeFormatter } from "../SubTypeFormatter"; import { BaseType } from "../Type/BaseType"; import { UnionType } from "../Type/UnionType"; import { TypeFormatter } from "../TypeFormatter"; +import { mergeDefinitions } from "../Utils/mergeDefinitions"; import { uniqueArray } from "../Utils/uniqueArray"; export class UnionTypeFormatter implements SubTypeFormatter { @@ -45,6 +46,18 @@ export class UnionTypeFormatter implements SubTypeFormatter { } } + for (let idx = 0; idx < flattenedDefinitions.length - 1; idx++) { + for (let comp = idx + 1; comp < flattenedDefinitions.length; ) { + const merged = mergeDefinitions(flattenedDefinitions[idx], flattenedDefinitions[comp]); + if (merged) { + flattenedDefinitions[idx] = merged; + flattenedDefinitions.splice(comp, 1); + } else { + comp++; + } + } + } + return flattenedDefinitions.length > 1 ? { anyOf: flattenedDefinitions, diff --git a/src/Utils/mergeDefinitions.ts b/src/Utils/mergeDefinitions.ts new file mode 100644 index 000000000..4c3caf0de --- /dev/null +++ b/src/Utils/mergeDefinitions.ts @@ -0,0 +1,25 @@ +import { Definition } from "../Schema/Definition"; +import { uniqueArray } from "./uniqueArray"; + +/** + * Attempt to merge two disjoint definitions into one. Definitions are disjoint + * (and therefore mergeable) if all of the following are true: + * 1) Each has a 'type' property, and they share no types in common, + * 2) The cross-type validation properties 'enum' and 'const' are not on either definition, and + * 3) The two definitions have no properties besides 'type' in common. + * + * Returns the merged definition, or null if the two defs were not disjoint. + */ +export function mergeDefinitions(def1: Definition, def2: Definition): Definition | null { + const { type: type1, ...props1 } = def1; + const { type: type2, ...props2 } = def2; + const types = [type1!, type2!].flat(); + if (!type1 || !type2 || uniqueArray(types).length !== types.length) { + return null; + } + const keys = [Object.keys(props1), Object.keys(props2)].flat(); + if (keys.includes("enum") || keys.includes("const") || uniqueArray(keys).length !== keys.length) { + return null; + } + return { type: types, ...props1, ...props2 }; +} From 6b0e19b49298abf1c2571d9ebe5c2a8c51272285 Mon Sep 17 00:00:00 2001 From: Danielle Church Date: Thu, 25 Mar 2021 17:42:24 -0400 Subject: [PATCH 2/7] Update src/Utils/mergeDefinitions.ts Change the array.flat() call to use spread notation, since both members are guaranteed to be arrays (unlike the types call, above). Thanks for the suggestion, @domoritz! Co-authored-by: Dominik Moritz --- src/Utils/mergeDefinitions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Utils/mergeDefinitions.ts b/src/Utils/mergeDefinitions.ts index 4c3caf0de..749392a27 100644 --- a/src/Utils/mergeDefinitions.ts +++ b/src/Utils/mergeDefinitions.ts @@ -17,7 +17,7 @@ export function mergeDefinitions(def1: Definition, def2: Definition): Definition if (!type1 || !type2 || uniqueArray(types).length !== types.length) { return null; } - const keys = [Object.keys(props1), Object.keys(props2)].flat(); + const keys = [...Object.keys(props1), ...Object.keys(props2)]; if (keys.includes("enum") || keys.includes("const") || uniqueArray(keys).length !== keys.length) { return null; } From 109b78e4296b2f21975527064a147d44b7c10e87 Mon Sep 17 00:00:00 2001 From: Danielle Church Date: Fri, 26 Mar 2021 00:20:43 -0400 Subject: [PATCH 3/7] Updating test snapshots; fixing reachability tests The vega-lite test originally worked after updating the snapshot, but there was a broken reference that got mistakenly purged by removeUnreachable (which assumed that any definition would only ever have a single property that could hold references). Added a validation check to vega-lite.test to register the failure, updated addReachable to check all possible properties (unless $ref is set; the spec says that everything else gets ignored in that case). --- src/Utils/removeUnreachable.ts | 57 +- .../type-mapped-double-exclude/schema.json | 24 +- test/valid-data/type-union/schema.json | 16 +- test/vega-lite.test.ts | 9 + test/vega-lite/schema.json | 1122 ++++++++--------- 5 files changed, 567 insertions(+), 661 deletions(-) diff --git a/src/Utils/removeUnreachable.ts b/src/Utils/removeUnreachable.ts index 1619fc3f9..8619e5638 100644 --- a/src/Utils/removeUnreachable.ts +++ b/src/Utils/removeUnreachable.ts @@ -20,38 +20,45 @@ function addReachable( } reachable.add(typeName); addReachable(definitions[typeName], definitions, reachable); - } else if (definition.anyOf) { - for (const def of definition.anyOf) { - addReachable(def, definitions, reachable); + } else { + if (definition.anyOf) { + for (const def of definition.anyOf) { + addReachable(def, definitions, reachable); + } } - } else if (definition.allOf) { - for (const def of definition.allOf) { - addReachable(def, definitions, reachable); + if (definition.allOf) { + for (const def of definition.allOf) { + addReachable(def, definitions, reachable); + } } - } else if (definition.oneOf) { - for (const def of definition.oneOf) { - addReachable(def, definitions, reachable); + if (definition.oneOf) { + for (const def of definition.oneOf) { + addReachable(def, definitions, reachable); + } } - } else if (definition.not) { - addReachable(definition.not, definitions, reachable); - } else if (definition.type === "object") { - for (const prop in definition.properties || {}) { - const propDefinition = definition.properties![prop]; - addReachable(propDefinition, definitions, reachable); + if (definition.not) { + addReachable(definition.not, definitions, reachable); } + if (definition.type === "object" || (Array.isArray(definition.type) && definition.type.includes("object"))) { + for (const prop in definition.properties || {}) { + const propDefinition = definition.properties![prop]; + addReachable(propDefinition, definitions, reachable); + } - const additionalProperties = definition.additionalProperties; - if (additionalProperties) { - addReachable(additionalProperties, definitions, reachable); + const additionalProperties = definition.additionalProperties; + if (additionalProperties) { + addReachable(additionalProperties, definitions, reachable); + } } - } else if (definition.type === "array") { - const items = definition.items; - if (isArray(items)) { - for (const item of items) { - addReachable(item, definitions, reachable); + if (definition.type === "array" || (Array.isArray(definition.type) && definition.type.includes("array"))) { + const items = definition.items; + if (isArray(items)) { + for (const item of items) { + addReachable(item, definitions, reachable); + } + } else if (items) { + addReachable(items, definitions, reachable); } - } else if (items) { - addReachable(items, definitions, reachable); } } } diff --git a/test/valid-data/type-mapped-double-exclude/schema.json b/test/valid-data/type-mapped-double-exclude/schema.json index 99b1a9a9b..16e42b471 100644 --- a/test/valid-data/type-mapped-double-exclude/schema.json +++ b/test/valid-data/type-mapped-double-exclude/schema.json @@ -6,25 +6,17 @@ "additionalProperties": false, "properties": { "bar": { - "anyOf": [ - { - "description": "Bar", - "type": "number" - }, - { - "type": "null" - } + "description": "Bar", + "type": [ + "number", + "null" ] }, "foo": { - "anyOf": [ - { - "description": "Foo", - "type": "number" - }, - { - "type": "null" - } + "description": "Foo", + "type": [ + "number", + "null" ] } }, diff --git a/test/valid-data/type-union/schema.json b/test/valid-data/type-union/schema.json index 043850f85..70d8e1b8e 100644 --- a/test/valid-data/type-union/schema.json +++ b/test/valid-data/type-union/schema.json @@ -12,16 +12,12 @@ ] }, "var2": { - "anyOf": [ - { - "type": "string" - }, - { - "items": { - "type": "number" - }, - "type": "array" - } + "items": { + "type": "number" + }, + "type": [ + "string", + "array" ] }, "var3": { diff --git a/test/vega-lite.test.ts b/test/vega-lite.test.ts index 3cbc5ce2b..529707330 100644 --- a/test/vega-lite.test.ts +++ b/test/vega-lite.test.ts @@ -3,6 +3,8 @@ import { resolve } from "path"; import { Config } from "../src/Config"; import { createGenerator } from "./utils"; import stringify from "json-stable-stringify"; +import Ajv from "ajv"; +import addFormats from "ajv-formats"; describe("vega-lite", () => { it("schema", () => { @@ -18,6 +20,13 @@ describe("vega-lite", () => { const schema = generator.createSchema(type); const schemaFile = resolve("test/vega-lite/schema.json"); + const validator = new Ajv({ strict: false }); + addFormats(validator); + + validator.validateSchema(schema); + expect(validator.errors).toBeNull(); + validator.compile(schema); // Will find MissingRef errors + if (process.env.UPDATE_SCHEMA) { writeFileSync(schemaFile, stringify(schema, { space: 2 }) + "\n", "utf8"); } diff --git a/test/vega-lite/schema.json b/test/vega-lite/schema.json index 3d7d2cac6..60a968ace 100644 --- a/test/vega-lite/schema.json +++ b/test/vega-lite/schema.json @@ -867,22 +867,18 @@ "tooltip": { "anyOf": [ { - "type": "number" - }, - { - "type": "string" - }, - { - "type": "boolean" + "type": [ + "number", + "string", + "boolean", + "null" + ] }, { "$ref": "#/definitions/TooltipContent" }, { "$ref": "#/definitions/ExprRef" - }, - { - "type": "null" } ], "description": "The tooltip text string to show upon mouse hover or an object defining which fields should the tooltip be derived from.\n\n- If `tooltip` is `true` or `{\"content\": \"encoding\"}`, then all fields from `encoding` will be used.\n- If `tooltip` is `{\"content\": \"data\"}`, then all fields that appear in the highlighted data point will be used.\n- If set to `null` or `false`, then no tooltip will be used.\n\nSee the [`tooltip`](https://vega.github.io/vega-lite/docs/tooltip.html) documentation for a detailed discussion about tooltip in Vega-Lite.\n\n__Default value:__ `null`" @@ -1558,18 +1554,14 @@ "description": "The anchor position of the axis in pixels. For x-axes with top or bottom orientation, this sets the axis group x coordinate. For y-axes with left or right orientation, this sets the axis group y coordinate.\n\n__Default value__: `0`" }, "style": { - "anyOf": [ - { - "type": "string" - }, - { - "items": { - "type": "string" - }, - "type": "array" - } - ], - "description": "A string or array of strings indicating the name of custom styles to apply to the axis. A style is a named collection of axis property defined within the [style configuration](https://vega.github.io/vega-lite/docs/mark.html#style-config). If style is an array, later styles will override earlier styles.\n\n__Default value:__ (none) __Note:__ Any specified style will augment the default style. For example, an x-axis mark with `\"style\": \"foo\"` will use `config.axisX` and `config.style.foo` (the specified style `\"foo\"` has higher precedence)." + "description": "A string or array of strings indicating the name of custom styles to apply to the axis. A style is a named collection of axis property defined within the [style configuration](https://vega.github.io/vega-lite/docs/mark.html#style-config). If style is an array, later styles will override earlier styles.\n\n__Default value:__ (none) __Note:__ Any specified style will augment the default style. For example, an x-axis mark with `\"style\": \"foo\"` will use `config.axisX` and `config.style.foo` (the specified style `\"foo\"` has higher precedence).", + "items": { + "type": "string" + }, + "type": [ + "string", + "array" + ] }, "tickBand": { "anyOf": [ @@ -2515,18 +2507,14 @@ "description": "The anchor position of the axis in pixels. For x-axes with top or bottom orientation, this sets the axis group x coordinate. For y-axes with left or right orientation, this sets the axis group y coordinate.\n\n__Default value__: `0`" }, "style": { - "anyOf": [ - { - "type": "string" - }, - { - "items": { - "type": "string" - }, - "type": "array" - } - ], - "description": "A string or array of strings indicating the name of custom styles to apply to the axis. A style is a named collection of axis property defined within the [style configuration](https://vega.github.io/vega-lite/docs/mark.html#style-config). If style is an array, later styles will override earlier styles.\n\n__Default value:__ (none) __Note:__ Any specified style will augment the default style. For example, an x-axis mark with `\"style\": \"foo\"` will use `config.axisX` and `config.style.foo` (the specified style `\"foo\"` has higher precedence)." + "description": "A string or array of strings indicating the name of custom styles to apply to the axis. A style is a named collection of axis property defined within the [style configuration](https://vega.github.io/vega-lite/docs/mark.html#style-config). If style is an array, later styles will override earlier styles.\n\n__Default value:__ (none) __Note:__ Any specified style will augment the default style. For example, an x-axis mark with `\"style\": \"foo\"` will use `config.axisX` and `config.style.foo` (the specified style `\"foo\"` has higher precedence).", + "items": { + "type": "string" + }, + "type": [ + "string", + "array" + ] }, "tickBand": { "anyOf": [ @@ -3686,22 +3674,18 @@ "tooltip": { "anyOf": [ { - "type": "number" - }, - { - "type": "string" - }, - { - "type": "boolean" + "type": [ + "number", + "string", + "boolean", + "null" + ] }, { "$ref": "#/definitions/TooltipContent" }, { "$ref": "#/definitions/ExprRef" - }, - { - "type": "null" } ], "description": "The tooltip text string to show upon mouse hover or an object defining which fields should the tooltip be derived from.\n\n- If `tooltip` is `true` or `{\"content\": \"encoding\"}`, then all fields from `encoding` will be used.\n- If `tooltip` is `{\"content\": \"data\"}`, then all fields that appear in the highlighted data point will be used.\n- If set to `null` or `false`, then no tooltip will be used.\n\nSee the [`tooltip`](https://vega.github.io/vega-lite/docs/tooltip.html) documentation for a detailed discussion about tooltip in Vega-Lite.\n\n__Default value:__ `null`" @@ -4947,10 +4931,10 @@ "items": { "$ref": "#/definitions/StringFieldDef" }, - "type": "array" - }, - { - "type": "null" + "type": [ + "array", + "null" + ] } ], "description": "The tooltip text to show upon mouse hover. Specifying `tooltip` encoding overrides [the `tooltip` property in the mark definition](https://vega.github.io/vega-lite/docs/mark.html#mark-def).\n\nSee the [`tooltip`](https://vega.github.io/vega-lite/docs/tooltip.html) documentation for a detailed discussion about tooltip in Vega-Lite." @@ -5534,18 +5518,14 @@ ] }, "value": { - "anyOf": [ - { - "items": { - "type": "number" - }, - "type": "array" - }, - { - "type": "null" - } - ], - "description": "A constant value in visual domain (e.g., `\"red\"` / `\"#0099ff\"` / [gradient definition](https://vega.github.io/vega-lite/docs/types.html#gradient) for color, values between `0` to `1` for opacity)." + "description": "A constant value in visual domain (e.g., `\"red\"` / `\"#0099ff\"` / [gradient definition](https://vega.github.io/vega-lite/docs/types.html#gradient) for color, values between `0` to `1` for opacity).", + "items": { + "type": "number" + }, + "type": [ + "array", + "null" + ] } }, "required": [ @@ -5726,13 +5706,13 @@ "bin": { "anyOf": [ { - "type": "boolean" + "type": [ + "boolean", + "null" + ] }, { "$ref": "#/definitions/BinParams" - }, - { - "type": "null" } ], "description": "A flag for binning a `quantitative` field, [an object defining binning parameters](https://vega.github.io/vega-lite/docs/bin.html#params), or indicating that the data for `x` or `y` channel are binned before they are imported into Vega-Lite (`\"binned\"`).\n\n- If `true`, default [binning parameters](https://vega.github.io/vega-lite/docs/bin.html) will be applied.\n\n- If `\"binned\"`, this indicates that the data for the `x` (or `y`) channel are already binned. You can map the bin-start field to `x` (or `y`) and the bin-end field to `x2` (or `y2`). The scale and axis will be formatted similar to binning in Vega-Lite. To adjust the axis ticks based on the bin step, you can also set the axis's [`tickMinStep`](https://vega.github.io/vega-lite/docs/axis.html#ticks) property.\n\n__Default value:__ `false`\n\n__See also:__ [`bin`](https://vega.github.io/vega-lite/docs/bin.html) documentation." @@ -5893,13 +5873,13 @@ "bin": { "anyOf": [ { - "type": "boolean" + "type": [ + "boolean", + "null" + ] }, { "$ref": "#/definitions/BinParams" - }, - { - "type": "null" } ], "description": "A flag for binning a `quantitative` field, [an object defining binning parameters](https://vega.github.io/vega-lite/docs/bin.html#params), or indicating that the data for `x` or `y` channel are binned before they are imported into Vega-Lite (`\"binned\"`).\n\n- If `true`, default [binning parameters](https://vega.github.io/vega-lite/docs/bin.html) will be applied.\n\n- If `\"binned\"`, this indicates that the data for the `x` (or `y`) channel are already binned. You can map the bin-start field to `x` (or `y`) and the bin-end field to `x2` (or `y2`). The scale and axis will be formatted similar to binning in Vega-Lite. To adjust the axis ticks based on the bin step, you can also set the axis's [`tickMinStep`](https://vega.github.io/vega-lite/docs/axis.html#ticks) property.\n\n__Default value:__ `false`\n\n__See also:__ [`bin`](https://vega.github.io/vega-lite/docs/bin.html) documentation." @@ -6058,7 +6038,10 @@ "bin": { "anyOf": [ { - "type": "boolean" + "type": [ + "boolean", + "null" + ] }, { "$ref": "#/definitions/BinParams" @@ -6066,9 +6049,6 @@ { "const": "binned", "type": "string" - }, - { - "type": "null" } ], "description": "A flag for binning a `quantitative` field, [an object defining binning parameters](https://vega.github.io/vega-lite/docs/bin.html#params), or indicating that the data for `x` or `y` channel are binned before they are imported into Vega-Lite (`\"binned\"`).\n\n- If `true`, default [binning parameters](https://vega.github.io/vega-lite/docs/bin.html) will be applied.\n\n- If `\"binned\"`, this indicates that the data for the `x` (or `y`) channel are already binned. You can map the bin-start field to `x` (or `y`) and the bin-end field to `x2` (or `y2`). The scale and axis will be formatted similar to binning in Vega-Lite. To adjust the axis ticks based on the bin step, you can also set the axis's [`tickMinStep`](https://vega.github.io/vega-lite/docs/axis.html#ticks) property.\n\n__Default value:__ `false`\n\n__See also:__ [`bin`](https://vega.github.io/vega-lite/docs/bin.html) documentation." @@ -6149,10 +6129,10 @@ "$ref": "#/definitions/Gradient" }, { - "type": "string" - }, - { - "type": "null" + "type": [ + "string", + "null" + ] }, { "$ref": "#/definitions/ExprRef" @@ -6300,10 +6280,10 @@ "value": { "anyOf": [ { - "type": "string" - }, - { - "type": "null" + "type": [ + "string", + "null" + ] }, { "$ref": "#/definitions/ExprRef" @@ -6585,18 +6565,14 @@ "description": "Predicate for triggering the condition" }, "value": { - "anyOf": [ - { - "items": { - "type": "number" - }, - "type": "array" - }, - { - "type": "null" - } - ], - "description": "A constant value in visual domain (e.g., `\"red\"` / `\"#0099ff\"` / [gradient definition](https://vega.github.io/vega-lite/docs/types.html#gradient) for color, values between `0` to `1` for opacity)." + "description": "A constant value in visual domain (e.g., `\"red\"` / `\"#0099ff\"` / [gradient definition](https://vega.github.io/vega-lite/docs/types.html#gradient) for color, values between `0` to `1` for opacity).", + "items": { + "type": "number" + }, + "type": [ + "array", + "null" + ] } }, "required": [ @@ -6729,13 +6705,13 @@ "bin": { "anyOf": [ { - "type": "boolean" + "type": [ + "boolean", + "null" + ] }, { "$ref": "#/definitions/BinParams" - }, - { - "type": "null" } ], "description": "A flag for binning a `quantitative` field, [an object defining binning parameters](https://vega.github.io/vega-lite/docs/bin.html#params), or indicating that the data for `x` or `y` channel are binned before they are imported into Vega-Lite (`\"binned\"`).\n\n- If `true`, default [binning parameters](https://vega.github.io/vega-lite/docs/bin.html) will be applied.\n\n- If `\"binned\"`, this indicates that the data for the `x` (or `y`) channel are already binned. You can map the bin-start field to `x` (or `y`) and the bin-end field to `x2` (or `y2`). The scale and axis will be formatted similar to binning in Vega-Lite. To adjust the axis ticks based on the bin step, you can also set the axis's [`tickMinStep`](https://vega.github.io/vega-lite/docs/axis.html#ticks) property.\n\n__Default value:__ `false`\n\n__See also:__ [`bin`](https://vega.github.io/vega-lite/docs/bin.html) documentation." @@ -6888,13 +6864,13 @@ "bin": { "anyOf": [ { - "type": "boolean" + "type": [ + "boolean", + "null" + ] }, { "$ref": "#/definitions/BinParams" - }, - { - "type": "null" } ], "description": "A flag for binning a `quantitative` field, [an object defining binning parameters](https://vega.github.io/vega-lite/docs/bin.html#params), or indicating that the data for `x` or `y` channel are binned before they are imported into Vega-Lite (`\"binned\"`).\n\n- If `true`, default [binning parameters](https://vega.github.io/vega-lite/docs/bin.html) will be applied.\n\n- If `\"binned\"`, this indicates that the data for the `x` (or `y`) channel are already binned. You can map the bin-start field to `x` (or `y`) and the bin-end field to `x2` (or `y2`). The scale and axis will be formatted similar to binning in Vega-Lite. To adjust the axis ticks based on the bin step, you can also set the axis's [`tickMinStep`](https://vega.github.io/vega-lite/docs/axis.html#ticks) property.\n\n__Default value:__ `false`\n\n__See also:__ [`bin`](https://vega.github.io/vega-lite/docs/bin.html) documentation." @@ -7045,7 +7021,10 @@ "bin": { "anyOf": [ { - "type": "boolean" + "type": [ + "boolean", + "null" + ] }, { "$ref": "#/definitions/BinParams" @@ -7053,9 +7032,6 @@ { "const": "binned", "type": "string" - }, - { - "type": "null" } ], "description": "A flag for binning a `quantitative` field, [an object defining binning parameters](https://vega.github.io/vega-lite/docs/bin.html#params), or indicating that the data for `x` or `y` channel are binned before they are imported into Vega-Lite (`\"binned\"`).\n\n- If `true`, default [binning parameters](https://vega.github.io/vega-lite/docs/bin.html) will be applied.\n\n- If `\"binned\"`, this indicates that the data for the `x` (or `y`) channel are already binned. You can map the bin-start field to `x` (or `y`) and the bin-end field to `x2` (or `y2`). The scale and axis will be formatted similar to binning in Vega-Lite. To adjust the axis ticks based on the bin step, you can also set the axis's [`tickMinStep`](https://vega.github.io/vega-lite/docs/axis.html#ticks) property.\n\n__Default value:__ `false`\n\n__See also:__ [`bin`](https://vega.github.io/vega-lite/docs/bin.html) documentation." @@ -7128,10 +7104,10 @@ "$ref": "#/definitions/Gradient" }, { - "type": "string" - }, - { - "type": "null" + "type": [ + "string", + "null" + ] }, { "$ref": "#/definitions/ExprRef" @@ -7259,10 +7235,10 @@ "value": { "anyOf": [ { - "type": "string" - }, - { - "type": "null" + "type": [ + "string", + "null" + ] }, { "$ref": "#/definitions/ExprRef" @@ -8612,13 +8588,13 @@ "bin": { "anyOf": [ { - "type": "boolean" + "type": [ + "boolean", + "null" + ] }, { "$ref": "#/definitions/BinParams" - }, - { - "type": "null" } ], "description": "A flag for binning a `quantitative` field, [an object defining binning parameters](https://vega.github.io/vega-lite/docs/bin.html#params), or indicating that the data for `x` or `y` channel are binned before they are imported into Vega-Lite (`\"binned\"`).\n\n- If `true`, default [binning parameters](https://vega.github.io/vega-lite/docs/bin.html) will be applied.\n\n- If `\"binned\"`, this indicates that the data for the `x` (or `y`) channel are already binned. You can map the bin-start field to `x` (or `y`) and the bin-end field to `x2` (or `y2`). The scale and axis will be formatted similar to binning in Vega-Lite. To adjust the axis ticks based on the bin step, you can also set the axis's [`tickMinStep`](https://vega.github.io/vega-lite/docs/axis.html#ticks) property.\n\n__Default value:__ `false`\n\n__See also:__ [`bin`](https://vega.github.io/vega-lite/docs/bin.html) documentation." @@ -8734,13 +8710,13 @@ "bin": { "anyOf": [ { - "type": "boolean" + "type": [ + "boolean", + "null" + ] }, { "$ref": "#/definitions/BinParams" - }, - { - "type": "null" } ], "description": "A flag for binning a `quantitative` field, [an object defining binning parameters](https://vega.github.io/vega-lite/docs/bin.html#params), or indicating that the data for `x` or `y` channel are binned before they are imported into Vega-Lite (`\"binned\"`).\n\n- If `true`, default [binning parameters](https://vega.github.io/vega-lite/docs/bin.html) will be applied.\n\n- If `\"binned\"`, this indicates that the data for the `x` (or `y`) channel are already binned. You can map the bin-start field to `x` (or `y`) and the bin-end field to `x2` (or `y2`). The scale and axis will be formatted similar to binning in Vega-Lite. To adjust the axis ticks based on the bin step, you can also set the axis's [`tickMinStep`](https://vega.github.io/vega-lite/docs/axis.html#ticks) property.\n\n__Default value:__ `false`\n\n__See also:__ [`bin`](https://vega.github.io/vega-lite/docs/bin.html) documentation." @@ -8984,10 +8960,10 @@ "items": { "$ref": "#/definitions/StringFieldDef" }, - "type": "array" - }, - { - "type": "null" + "type": [ + "array", + "null" + ] } ], "description": "The tooltip text to show upon mouse hover. Specifying `tooltip` encoding overrides [the `tooltip` property in the mark definition](https://vega.github.io/vega-lite/docs/mark.html#mark-def).\n\nSee the [`tooltip`](https://vega.github.io/vega-lite/docs/tooltip.html) documentation for a detailed discussion about tooltip in Vega-Lite." @@ -9238,13 +9214,11 @@ "equal": { "anyOf": [ { - "type": "string" - }, - { - "type": "number" - }, - { - "type": "boolean" + "type": [ + "string", + "number", + "boolean" + ] }, { "$ref": "#/definitions/DateTime" @@ -9287,10 +9261,10 @@ "gte": { "anyOf": [ { - "type": "string" - }, - { - "type": "number" + "type": [ + "string", + "number" + ] }, { "$ref": "#/definitions/DateTime" @@ -9329,10 +9303,10 @@ "gt": { "anyOf": [ { - "type": "string" - }, - { - "type": "number" + "type": [ + "string", + "number" + ] }, { "$ref": "#/definitions/DateTime" @@ -9371,10 +9345,10 @@ "lte": { "anyOf": [ { - "type": "string" - }, - { - "type": "number" + "type": [ + "string", + "number" + ] }, { "$ref": "#/definitions/DateTime" @@ -9413,10 +9387,10 @@ "lt": { "anyOf": [ { - "type": "string" - }, - { - "type": "number" + "type": [ + "string", + "number" + ] }, { "$ref": "#/definitions/DateTime" @@ -9711,13 +9685,13 @@ "bin": { "anyOf": [ { - "type": "boolean" + "type": [ + "boolean", + "null" + ] }, { "$ref": "#/definitions/BinParams" - }, - { - "type": "null" } ], "description": "A flag for binning a `quantitative` field, [an object defining binning parameters](https://vega.github.io/vega-lite/docs/bin.html#params), or indicating that the data for `x` or `y` channel are binned before they are imported into Vega-Lite (`\"binned\"`).\n\n- If `true`, default [binning parameters](https://vega.github.io/vega-lite/docs/bin.html) will be applied.\n\n- If `\"binned\"`, this indicates that the data for the `x` (or `y`) channel are already binned. You can map the bin-start field to `x` (or `y`) and the bin-end field to `x2` (or `y2`). The scale and axis will be formatted similar to binning in Vega-Lite. To adjust the axis ticks based on the bin step, you can also set the axis's [`tickMinStep`](https://vega.github.io/vega-lite/docs/axis.html#ticks) property.\n\n__Default value:__ `false`\n\n__See also:__ [`bin`](https://vega.github.io/vega-lite/docs/bin.html) documentation." @@ -9812,13 +9786,13 @@ "bin": { "anyOf": [ { - "type": "boolean" + "type": [ + "boolean", + "null" + ] }, { "$ref": "#/definitions/BinParams" - }, - { - "type": "null" } ], "description": "A flag for binning a `quantitative` field, [an object defining binning parameters](https://vega.github.io/vega-lite/docs/bin.html#params), or indicating that the data for `x` or `y` channel are binned before they are imported into Vega-Lite (`\"binned\"`).\n\n- If `true`, default [binning parameters](https://vega.github.io/vega-lite/docs/bin.html) will be applied.\n\n- If `\"binned\"`, this indicates that the data for the `x` (or `y`) channel are already binned. You can map the bin-start field to `x` (or `y`) and the bin-end field to `x2` (or `y2`). The scale and axis will be formatted similar to binning in Vega-Lite. To adjust the axis ticks based on the bin step, you can also set the axis's [`tickMinStep`](https://vega.github.io/vega-lite/docs/axis.html#ticks) property.\n\n__Default value:__ `false`\n\n__See also:__ [`bin`](https://vega.github.io/vega-lite/docs/bin.html) documentation." @@ -9913,13 +9887,13 @@ "bin": { "anyOf": [ { - "type": "boolean" + "type": [ + "boolean", + "null" + ] }, { "$ref": "#/definitions/BinParams" - }, - { - "type": "null" } ], "description": "A flag for binning a `quantitative` field, [an object defining binning parameters](https://vega.github.io/vega-lite/docs/bin.html#params), or indicating that the data for `x` or `y` channel are binned before they are imported into Vega-Lite (`\"binned\"`).\n\n- If `true`, default [binning parameters](https://vega.github.io/vega-lite/docs/bin.html) will be applied.\n\n- If `\"binned\"`, this indicates that the data for the `x` (or `y`) channel are already binned. You can map the bin-start field to `x` (or `y`) and the bin-end field to `x2` (or `y2`). The scale and axis will be formatted similar to binning in Vega-Lite. To adjust the axis ticks based on the bin step, you can also set the axis's [`tickMinStep`](https://vega.github.io/vega-lite/docs/axis.html#ticks) property.\n\n__Default value:__ `false`\n\n__See also:__ [`bin`](https://vega.github.io/vega-lite/docs/bin.html) documentation." @@ -10014,13 +9988,13 @@ "bin": { "anyOf": [ { - "type": "boolean" - }, - { - "$ref": "#/definitions/BinParams" + "type": [ + "boolean", + "null" + ] }, { - "type": "null" + "$ref": "#/definitions/BinParams" } ], "description": "A flag for binning a `quantitative` field, [an object defining binning parameters](https://vega.github.io/vega-lite/docs/bin.html#params), or indicating that the data for `x` or `y` channel are binned before they are imported into Vega-Lite (`\"binned\"`).\n\n- If `true`, default [binning parameters](https://vega.github.io/vega-lite/docs/bin.html) will be applied.\n\n- If `\"binned\"`, this indicates that the data for the `x` (or `y`) channel are already binned. You can map the bin-start field to `x` (or `y`) and the bin-end field to `x2` (or `y2`). The scale and axis will be formatted similar to binning in Vega-Lite. To adjust the axis ticks based on the bin step, you can also set the axis's [`tickMinStep`](https://vega.github.io/vega-lite/docs/axis.html#ticks) property.\n\n__Default value:__ `false`\n\n__See also:__ [`bin`](https://vega.github.io/vega-lite/docs/bin.html) documentation." @@ -10178,7 +10152,10 @@ "bin": { "anyOf": [ { - "type": "boolean" + "type": [ + "boolean", + "null" + ] }, { "$ref": "#/definitions/BinParams" @@ -10186,9 +10163,6 @@ { "const": "binned", "type": "string" - }, - { - "type": "null" } ], "description": "A flag for binning a `quantitative` field, [an object defining binning parameters](https://vega.github.io/vega-lite/docs/bin.html#params), or indicating that the data for `x` or `y` channel are binned before they are imported into Vega-Lite (`\"binned\"`).\n\n- If `true`, default [binning parameters](https://vega.github.io/vega-lite/docs/bin.html) will be applied.\n\n- If `\"binned\"`, this indicates that the data for the `x` (or `y`) channel are already binned. You can map the bin-start field to `x` (or `y`) and the bin-end field to `x2` (or `y2`). The scale and axis will be formatted similar to binning in Vega-Lite. To adjust the axis ticks based on the bin step, you can also set the axis's [`tickMinStep`](https://vega.github.io/vega-lite/docs/axis.html#ticks) property.\n\n__Default value:__ `false`\n\n__See also:__ [`bin`](https://vega.github.io/vega-lite/docs/bin.html) documentation." @@ -10272,7 +10246,10 @@ "bin": { "anyOf": [ { - "type": "boolean" + "type": [ + "boolean", + "null" + ] }, { "$ref": "#/definitions/BinParams" @@ -10280,9 +10257,6 @@ { "const": "binned", "type": "string" - }, - { - "type": "null" } ], "description": "A flag for binning a `quantitative` field, [an object defining binning parameters](https://vega.github.io/vega-lite/docs/bin.html#params), or indicating that the data for `x` or `y` channel are binned before they are imported into Vega-Lite (`\"binned\"`).\n\n- If `true`, default [binning parameters](https://vega.github.io/vega-lite/docs/bin.html) will be applied.\n\n- If `\"binned\"`, this indicates that the data for the `x` (or `y`) channel are already binned. You can map the bin-start field to `x` (or `y`) and the bin-end field to `x2` (or `y2`). The scale and axis will be formatted similar to binning in Vega-Lite. To adjust the axis ticks based on the bin step, you can also set the axis's [`tickMinStep`](https://vega.github.io/vega-lite/docs/axis.html#ticks) property.\n\n__Default value:__ `false`\n\n__See also:__ [`bin`](https://vega.github.io/vega-lite/docs/bin.html) documentation." @@ -10362,14 +10336,14 @@ "items": { "anyOf": [ { - "type": "number" + "type": [ + "number", + "null" + ] }, { "$ref": "#/definitions/DateTime" }, - { - "type": "null" - }, { "$ref": "#/definitions/ExprRef" } @@ -11845,7 +11819,11 @@ "items": { "type": "number" }, - "type": "array" + "type": [ + "array", + "string", + "object" + ] }, { "items": { @@ -11864,12 +11842,6 @@ "type": "object" }, "type": "array" - }, - { - "type": "string" - }, - { - "type": "object" } ] }, @@ -11929,10 +11901,10 @@ "$ref": "#/definitions/Stream" }, { - "type": "string" - }, - { - "type": "boolean" + "type": [ + "string", + "boolean" + ] } ], "description": "Clears the selection, emptying it of all values. This property can be a [Event Stream](https://vega.github.io/vega/docs/event-streams/) or `false` to disable clear.\n\n__Default value:__ `dblclick`.\n\n__See also:__ [`clear` examples ](https://vega.github.io/vega-lite/docs/selection.html#clear) in the documentation." @@ -12004,10 +11976,10 @@ "$ref": "#/definitions/Stream" }, { - "type": "string" - }, - { - "type": "boolean" + "type": [ + "string", + "boolean" + ] } ], "description": "Clears the selection, emptying it of all values. This property can be a [Event Stream](https://vega.github.io/vega/docs/event-streams/) or `false` to disable clear.\n\n__Default value:__ `dblclick`.\n\n__See also:__ [`clear` examples ](https://vega.github.io/vega-lite/docs/selection.html#clear) in the documentation." @@ -14870,22 +14842,18 @@ "tooltip": { "anyOf": [ { - "type": "number" - }, - { - "type": "string" - }, - { - "type": "boolean" + "type": [ + "number", + "string", + "boolean", + "null" + ] }, { "$ref": "#/definitions/TooltipContent" }, { "$ref": "#/definitions/ExprRef" - }, - { - "type": "null" } ], "description": "The tooltip text string to show upon mouse hover or an object defining which fields should the tooltip be derived from.\n\n- If `tooltip` is `true` or `{\"content\": \"encoding\"}`, then all fields from `encoding` will be used.\n- If `tooltip` is `{\"content\": \"data\"}`, then all fields that appear in the highlighted data point will be used.\n- If set to `null` or `false`, then no tooltip will be used.\n\nSee the [`tooltip`](https://vega.github.io/vega-lite/docs/tooltip.html) documentation for a detailed discussion about tooltip in Vega-Lite.\n\n__Default value:__ `null`" @@ -15968,22 +15936,18 @@ "tooltip": { "anyOf": [ { - "type": "number" - }, - { - "type": "string" - }, - { - "type": "boolean" + "type": [ + "number", + "string", + "boolean", + "null" + ] }, { "$ref": "#/definitions/TooltipContent" }, { "$ref": "#/definitions/ExprRef" - }, - { - "type": "null" } ], "description": "The tooltip text string to show upon mouse hover or an object defining which fields should the tooltip be derived from.\n\n- If `tooltip` is `true` or `{\"content\": \"encoding\"}`, then all fields from `encoding` will be used.\n- If `tooltip` is `{\"content\": \"data\"}`, then all fields that appear in the highlighted data point will be used.\n- If set to `null` or `false`, then no tooltip will be used.\n\nSee the [`tooltip`](https://vega.github.io/vega-lite/docs/tooltip.html) documentation for a detailed discussion about tooltip in Vega-Lite.\n\n__Default value:__ `null`" @@ -16782,18 +16746,14 @@ ] }, "style": { - "anyOf": [ - { - "type": "string" - }, - { - "items": { - "type": "string" - }, - "type": "array" - } - ], - "description": "A string or array of strings indicating the name of custom styles to apply to the mark. A style is a named collection of mark property defaults defined within the [style configuration](https://vega.github.io/vega-lite/docs/mark.html#style-config). If style is an array, later styles will override earlier styles. Any [mark properties](https://vega.github.io/vega-lite/docs/encoding.html#mark-prop) explicitly defined within the `encoding` will override a style default.\n\n__Default value:__ The mark's name. For example, a bar mark will have style `\"bar\"` by default. __Note:__ Any specified style will augment the default style. For example, a bar mark with `\"style\": \"foo\"` will receive from `config.style.bar` and `config.style.foo` (the specified style `\"foo\"` has higher precedence)." + "description": "A string or array of strings indicating the name of custom styles to apply to the mark. A style is a named collection of mark property defaults defined within the [style configuration](https://vega.github.io/vega-lite/docs/mark.html#style-config). If style is an array, later styles will override earlier styles. Any [mark properties](https://vega.github.io/vega-lite/docs/encoding.html#mark-prop) explicitly defined within the `encoding` will override a style default.\n\n__Default value:__ The mark's name. For example, a bar mark will have style `\"bar\"` by default. __Note:__ Any specified style will augment the default style. For example, a bar mark with `\"style\": \"foo\"` will receive from `config.style.bar` and `config.style.foo` (the specified style `\"foo\"` has higher precedence).", + "items": { + "type": "string" + }, + "type": [ + "string", + "array" + ] }, "tension": { "anyOf": [ @@ -16879,22 +16839,18 @@ "tooltip": { "anyOf": [ { - "type": "number" - }, - { - "type": "string" - }, - { - "type": "boolean" + "type": [ + "number", + "string", + "boolean", + "null" + ] }, { "$ref": "#/definitions/TooltipContent" }, { "$ref": "#/definitions/ExprRef" - }, - { - "type": "null" } ], "description": "The tooltip text string to show upon mouse hover or an object defining which fields should the tooltip be derived from.\n\n- If `tooltip` is `true` or `{\"content\": \"encoding\"}`, then all fields from `encoding` will be used.\n- If `tooltip` is `{\"content\": \"data\"}`, then all fields that appear in the highlighted data point will be used.\n- If set to `null` or `false`, then no tooltip will be used.\n\nSee the [`tooltip`](https://vega.github.io/vega-lite/docs/tooltip.html) documentation for a detailed discussion about tooltip in Vega-Lite.\n\n__Default value:__ `null`" @@ -17354,7 +17310,10 @@ "bin": { "anyOf": [ { - "type": "boolean" + "type": [ + "boolean", + "null" + ] }, { "$ref": "#/definitions/BinParams" @@ -17362,9 +17321,6 @@ { "const": "binned", "type": "string" - }, - { - "type": "null" } ], "description": "A flag for binning a `quantitative` field, [an object defining binning parameters](https://vega.github.io/vega-lite/docs/bin.html#params), or indicating that the data for `x` or `y` channel are binned before they are imported into Vega-Lite (`\"binned\"`).\n\n- If `true`, default [binning parameters](https://vega.github.io/vega-lite/docs/bin.html) will be applied.\n\n- If `\"binned\"`, this indicates that the data for the `x` (or `y`) channel are already binned. You can map the bin-start field to `x` (or `y`) and the bin-end field to `x2` (or `y2`). The scale and axis will be formatted similar to binning in Vega-Lite. To adjust the axis ticks based on the bin step, you can also set the axis's [`tickMinStep`](https://vega.github.io/vega-lite/docs/axis.html#ticks) property.\n\n__Default value:__ `false`\n\n__See also:__ [`bin`](https://vega.github.io/vega-lite/docs/bin.html) documentation." @@ -18120,18 +18076,14 @@ ] }, "style": { - "anyOf": [ - { - "type": "string" - }, - { - "items": { - "type": "string" - }, - "type": "array" - } - ], - "description": "A string or array of strings indicating the name of custom styles to apply to the mark. A style is a named collection of mark property defaults defined within the [style configuration](https://vega.github.io/vega-lite/docs/mark.html#style-config). If style is an array, later styles will override earlier styles. Any [mark properties](https://vega.github.io/vega-lite/docs/encoding.html#mark-prop) explicitly defined within the `encoding` will override a style default.\n\n__Default value:__ The mark's name. For example, a bar mark will have style `\"bar\"` by default. __Note:__ Any specified style will augment the default style. For example, a bar mark with `\"style\": \"foo\"` will receive from `config.style.bar` and `config.style.foo` (the specified style `\"foo\"` has higher precedence)." + "description": "A string or array of strings indicating the name of custom styles to apply to the mark. A style is a named collection of mark property defaults defined within the [style configuration](https://vega.github.io/vega-lite/docs/mark.html#style-config). If style is an array, later styles will override earlier styles. Any [mark properties](https://vega.github.io/vega-lite/docs/encoding.html#mark-prop) explicitly defined within the `encoding` will override a style default.\n\n__Default value:__ The mark's name. For example, a bar mark will have style `\"bar\"` by default. __Note:__ Any specified style will augment the default style. For example, a bar mark with `\"style\": \"foo\"` will receive from `config.style.bar` and `config.style.foo` (the specified style `\"foo\"` has higher precedence).", + "items": { + "type": "string" + }, + "type": [ + "string", + "array" + ] }, "tension": { "anyOf": [ @@ -18212,22 +18164,18 @@ "tooltip": { "anyOf": [ { - "type": "number" - }, - { - "type": "string" - }, - { - "type": "boolean" + "type": [ + "number", + "string", + "boolean", + "null" + ] }, { "$ref": "#/definitions/TooltipContent" }, { "$ref": "#/definitions/ExprRef" - }, - { - "type": "null" } ], "description": "The tooltip text string to show upon mouse hover or an object defining which fields should the tooltip be derived from.\n\n- If `tooltip` is `true` or `{\"content\": \"encoding\"}`, then all fields from `encoding` will be used.\n- If `tooltip` is `{\"content\": \"data\"}`, then all fields that appear in the highlighted data point will be used.\n- If set to `null` or `false`, then no tooltip will be used.\n\nSee the [`tooltip`](https://vega.github.io/vega-lite/docs/tooltip.html) documentation for a detailed discussion about tooltip in Vega-Lite.\n\n__Default value:__ `null`" @@ -18362,30 +18310,26 @@ "type": "object" }, "Padding": { - "anyOf": [ - { + "additionalProperties": false, + "minimum": 0, + "properties": { + "bottom": { "type": "number" }, - { - "additionalProperties": false, - "properties": { - "bottom": { - "type": "number" - }, - "left": { - "type": "number" - }, - "right": { - "type": "number" - }, - "top": { - "type": "number" - } - }, - "type": "object" + "left": { + "type": "number" + }, + "right": { + "type": "number" + }, + "top": { + "type": "number" } - ], - "minimum": 0 + }, + "type": [ + "number", + "object" + ] }, "ParameterExtent": { "anyOf": [ @@ -18454,10 +18398,10 @@ "ParseValue": { "anyOf": [ { - "type": "null" - }, - { - "type": "string" + "type": [ + "null", + "string" + ] }, { "const": "string", @@ -18519,10 +18463,10 @@ "$ref": "#/definitions/Stream" }, { - "type": "string" - }, - { - "type": "boolean" + "type": [ + "string", + "boolean" + ] } ], "description": "Clears the selection, emptying it of all values. This property can be a [Event Stream](https://vega.github.io/vega/docs/event-streams/) or `false` to disable clear.\n\n__Default value:__ `dblclick`.\n\n__See also:__ [`clear` examples ](https://vega.github.io/vega-lite/docs/selection.html#clear) in the documentation." @@ -18587,10 +18531,10 @@ "$ref": "#/definitions/Stream" }, { - "type": "string" - }, - { - "type": "boolean" + "type": [ + "string", + "boolean" + ] } ], "description": "Clears the selection, emptying it of all values. This property can be a [Event Stream](https://vega.github.io/vega/docs/event-streams/) or `false` to disable clear.\n\n__Default value:__ `dblclick`.\n\n__See also:__ [`clear` examples ](https://vega.github.io/vega-lite/docs/selection.html#clear) in the documentation." @@ -18729,10 +18673,10 @@ "$ref": "#/definitions/StackOffset" }, { - "type": "null" - }, - { - "type": "boolean" + "type": [ + "null", + "boolean" + ] } ], "description": "Type of stacking offset if the field should be stacked. `stack` is only applicable for `x`, `y`, `theta`, and `radius` channels with continuous domains. For example, `stack` of `y` can be used to customize stacking for a vertical bar chart.\n\n`stack` can be one of the following values:\n- `\"zero\"` or `true`: stacking with baseline offset at zero value of the scale (for creating typical stacked [bar](https://vega.github.io/vega-lite/docs/stack.html#bar) and [area](https://vega.github.io/vega-lite/docs/stack.html#area) chart).\n- `\"normalize\"` - stacking with normalized domain (for creating [normalized stacked bar and area charts](https://vega.github.io/vega-lite/docs/stack.html#normalized).
\n-`\"center\"` - stacking with center baseline (for [streamgraph](https://vega.github.io/vega-lite/docs/stack.html#streamgraph)).\n- `null` or `false` - No-stacking. This will produce layered [bar](https://vega.github.io/vega-lite/docs/stack.html#layered-bar-chart) and area chart.\n\n__Default value:__ `zero` for plots with all of the following conditions are true: (1) the mark is `bar`, `area`, or `arc`; (2) the stacked measure channel (x or y) has a linear scale; (3) At least one of non-position channels mapped to an unaggregated field that is different from x and y. Otherwise, `null` by default.\n\n__See also:__ [`stack`](https://vega.github.io/vega-lite/docs/stack.html) documentation." @@ -18787,10 +18731,10 @@ "$ref": "#/definitions/StackOffset" }, { - "type": "null" - }, - { - "type": "boolean" + "type": [ + "null", + "boolean" + ] } ], "description": "Type of stacking offset if the field should be stacked. `stack` is only applicable for `x`, `y`, `theta`, and `radius` channels with continuous domains. For example, `stack` of `y` can be used to customize stacking for a vertical bar chart.\n\n`stack` can be one of the following values:\n- `\"zero\"` or `true`: stacking with baseline offset at zero value of the scale (for creating typical stacked [bar](https://vega.github.io/vega-lite/docs/stack.html#bar) and [area](https://vega.github.io/vega-lite/docs/stack.html#area) chart).\n- `\"normalize\"` - stacking with normalized domain (for creating [normalized stacked bar and area charts](https://vega.github.io/vega-lite/docs/stack.html#normalized).
\n-`\"center\"` - stacking with center baseline (for [streamgraph](https://vega.github.io/vega-lite/docs/stack.html#streamgraph)).\n- `null` or `false` - No-stacking. This will produce layered [bar](https://vega.github.io/vega-lite/docs/stack.html#layered-bar-chart) and area chart.\n\n__Default value:__ `zero` for plots with all of the following conditions are true: (1) the mark is `bar`, `area`, or `arc`; (2) the stacked measure channel (x or y) has a linear scale; (3) At least one of non-position channels mapped to an unaggregated field that is different from x and y. Otherwise, `null` by default.\n\n__See also:__ [`stack`](https://vega.github.io/vega-lite/docs/stack.html) documentation." @@ -18842,7 +18786,10 @@ "bin": { "anyOf": [ { - "type": "boolean" + "type": [ + "boolean", + "null" + ] }, { "$ref": "#/definitions/BinParams" @@ -18850,9 +18797,6 @@ { "const": "binned", "type": "string" - }, - { - "type": "null" } ], "description": "A flag for binning a `quantitative` field, [an object defining binning parameters](https://vega.github.io/vega-lite/docs/bin.html#params), or indicating that the data for `x` or `y` channel are binned before they are imported into Vega-Lite (`\"binned\"`).\n\n- If `true`, default [binning parameters](https://vega.github.io/vega-lite/docs/bin.html) will be applied.\n\n- If `\"binned\"`, this indicates that the data for the `x` (or `y`) channel are already binned. You can map the bin-start field to `x` (or `y`) and the bin-end field to `x2` (or `y2`). The scale and axis will be formatted similar to binning in Vega-Lite. To adjust the axis ticks based on the bin step, you can also set the axis's [`tickMinStep`](https://vega.github.io/vega-lite/docs/axis.html#ticks) property.\n\n__Default value:__ `false`\n\n__See also:__ [`bin`](https://vega.github.io/vega-lite/docs/bin.html) documentation." @@ -18893,10 +18837,10 @@ "$ref": "#/definitions/StackOffset" }, { - "type": "null" - }, - { - "type": "boolean" + "type": [ + "null", + "boolean" + ] } ], "description": "Type of stacking offset if the field should be stacked. `stack` is only applicable for `x`, `y`, `theta`, and `radius` channels with continuous domains. For example, `stack` of `y` can be used to customize stacking for a vertical bar chart.\n\n`stack` can be one of the following values:\n- `\"zero\"` or `true`: stacking with baseline offset at zero value of the scale (for creating typical stacked [bar](https://vega.github.io/vega-lite/docs/stack.html#bar) and [area](https://vega.github.io/vega-lite/docs/stack.html#area) chart).\n- `\"normalize\"` - stacking with normalized domain (for creating [normalized stacked bar and area charts](https://vega.github.io/vega-lite/docs/stack.html#normalized).
\n-`\"center\"` - stacking with center baseline (for [streamgraph](https://vega.github.io/vega-lite/docs/stack.html#streamgraph)).\n- `null` or `false` - No-stacking. This will produce layered [bar](https://vega.github.io/vega-lite/docs/stack.html#layered-bar-chart) and area chart.\n\n__Default value:__ `zero` for plots with all of the following conditions are true: (1) the mark is `bar`, `area`, or `arc`; (2) the stacked measure channel (x or y) has a linear scale; (3) At least one of non-position channels mapped to an unaggregated field that is different from x and y. Otherwise, `null` by default.\n\n__See also:__ [`stack`](https://vega.github.io/vega-lite/docs/stack.html) documentation." @@ -18946,7 +18890,10 @@ "bin": { "anyOf": [ { - "type": "boolean" + "type": [ + "boolean", + "null" + ] }, { "$ref": "#/definitions/BinParams" @@ -18954,9 +18901,6 @@ { "const": "binned", "type": "string" - }, - { - "type": "null" } ], "description": "A flag for binning a `quantitative` field, [an object defining binning parameters](https://vega.github.io/vega-lite/docs/bin.html#params), or indicating that the data for `x` or `y` channel are binned before they are imported into Vega-Lite (`\"binned\"`).\n\n- If `true`, default [binning parameters](https://vega.github.io/vega-lite/docs/bin.html) will be applied.\n\n- If `\"binned\"`, this indicates that the data for the `x` (or `y`) channel are already binned. You can map the bin-start field to `x` (or `y`) and the bin-end field to `x2` (or `y2`). The scale and axis will be formatted similar to binning in Vega-Lite. To adjust the axis ticks based on the bin step, you can also set the axis's [`tickMinStep`](https://vega.github.io/vega-lite/docs/axis.html#ticks) property.\n\n__Default value:__ `false`\n\n__See also:__ [`bin`](https://vega.github.io/vega-lite/docs/bin.html) documentation." @@ -18986,10 +18930,10 @@ "$ref": "#/definitions/StackOffset" }, { - "type": "null" - }, - { - "type": "boolean" + "type": [ + "null", + "boolean" + ] } ], "description": "Type of stacking offset if the field should be stacked. `stack` is only applicable for `x`, `y`, `theta`, and `radius` channels with continuous domains. For example, `stack` of `y` can be used to customize stacking for a vertical bar chart.\n\n`stack` can be one of the following values:\n- `\"zero\"` or `true`: stacking with baseline offset at zero value of the scale (for creating typical stacked [bar](https://vega.github.io/vega-lite/docs/stack.html#bar) and [area](https://vega.github.io/vega-lite/docs/stack.html#area) chart).\n- `\"normalize\"` - stacking with normalized domain (for creating [normalized stacked bar and area charts](https://vega.github.io/vega-lite/docs/stack.html#normalized).
\n-`\"center\"` - stacking with center baseline (for [streamgraph](https://vega.github.io/vega-lite/docs/stack.html#streamgraph)).\n- `null` or `false` - No-stacking. This will produce layered [bar](https://vega.github.io/vega-lite/docs/stack.html#layered-bar-chart) and area chart.\n\n__Default value:__ `zero` for plots with all of the following conditions are true: (1) the mark is `bar`, `area`, or `arc`; (2) the stacked measure channel (x or y) has a linear scale; (3) At least one of non-position channels mapped to an unaggregated field that is different from x and y. Otherwise, `null` by default.\n\n__See also:__ [`stack`](https://vega.github.io/vega-lite/docs/stack.html) documentation." @@ -19566,16 +19510,12 @@ "items": { "anyOf": [ { - "type": "null" - }, - { - "type": "boolean" - }, - { - "type": "string" - }, - { - "type": "number" + "type": [ + "null", + "boolean", + "string", + "number" + ] }, { "$ref": "#/definitions/RangeRawArray" @@ -19612,14 +19552,14 @@ }, "scheme": { "anyOf": [ - { - "type": "string" - }, { "items": { "type": "string" }, - "type": "array" + "type": [ + "string", + "array" + ] }, { "$ref": "#/definitions/ColorScheme" @@ -20350,22 +20290,18 @@ "tooltip": { "anyOf": [ { - "type": "number" - }, - { - "type": "string" - }, - { - "type": "boolean" + "type": [ + "number", + "string", + "boolean", + "null" + ] }, { "$ref": "#/definitions/TooltipContent" }, { "$ref": "#/definitions/ExprRef" - }, - { - "type": "null" } ], "description": "The tooltip text string to show upon mouse hover or an object defining which fields should the tooltip be derived from.\n\n- If `tooltip` is `true` or `{\"content\": \"encoding\"}`, then all fields from `encoding` will be used.\n- If `tooltip` is `{\"content\": \"data\"}`, then all fields that appear in the highlighted data point will be used.\n- If set to `null` or `false`, then no tooltip will be used.\n\nSee the [`tooltip`](https://vega.github.io/vega-lite/docs/tooltip.html) documentation for a detailed discussion about tooltip in Vega-Lite.\n\n__Default value:__ `null`" @@ -20659,13 +20595,13 @@ "bin": { "anyOf": [ { - "type": "boolean" + "type": [ + "boolean", + "null" + ] }, { "$ref": "#/definitions/BinParams" - }, - { - "type": "null" } ], "description": "A flag for binning a `quantitative` field, [an object defining binning parameters](https://vega.github.io/vega-lite/docs/bin.html#params), or indicating that the data for `x` or `y` channel are binned before they are imported into Vega-Lite (`\"binned\"`).\n\n- If `true`, default [binning parameters](https://vega.github.io/vega-lite/docs/bin.html) will be applied.\n\n- If `\"binned\"`, this indicates that the data for the `x` (or `y`) channel are already binned. You can map the bin-start field to `x` (or `y`) and the bin-end field to `x2` (or `y2`). The scale and axis will be formatted similar to binning in Vega-Lite. To adjust the axis ticks based on the bin step, you can also set the axis's [`tickMinStep`](https://vega.github.io/vega-lite/docs/axis.html#ticks) property.\n\n__Default value:__ `false`\n\n__See also:__ [`bin`](https://vega.github.io/vega-lite/docs/bin.html) documentation." @@ -20809,16 +20745,12 @@ "items": { "anyOf": [ { - "type": "null" - }, - { - "type": "string" - }, - { - "type": "number" - }, - { - "type": "boolean" + "type": [ + "null", + "string", + "number", + "boolean" + ] }, { "$ref": "#/definitions/DateTime" @@ -20913,10 +20845,10 @@ "nice": { "anyOf": [ { - "type": "boolean" - }, - { - "type": "number" + "type": [ + "boolean", + "number" + ] }, { "$ref": "#/definitions/TimeInterval" @@ -20974,29 +20906,24 @@ "$ref": "#/definitions/RangeEnum" }, { + "additionalProperties": false, "items": { "anyOf": [ - { - "type": "number" - }, - { - "type": "string" - }, { "items": { "type": "number" }, - "type": "array" + "type": [ + "number", + "string", + "array" + ] }, { "$ref": "#/definitions/ExprRef" } ] }, - "type": "array" - }, - { - "additionalProperties": false, "properties": { "field": { "type": "string" @@ -21005,7 +20932,10 @@ "required": [ "field" ], - "type": "object" + "type": [ + "array", + "object" + ] } ], "description": "The range of the scale. One of:\n\n- A string indicating a [pre-defined named scale range](https://vega.github.io/vega-lite/docs/scale.html#range-config) (e.g., example, `\"symbol\"`, or `\"diverging\"`).\n\n- For [continuous scales](https://vega.github.io/vega-lite/docs/scale.html#continuous), two-element array indicating minimum and maximum values, or an array with more than two entries for specifying a [piecewise scale](https://vega.github.io/vega-lite/docs/scale.html#piecewise).\n\n- For [discrete](https://vega.github.io/vega-lite/docs/scale.html#discrete) and [discretizing](https://vega.github.io/vega-lite/docs/scale.html#discretizing) scales, an array of desired output values or an object with a `field` property representing the range values. For example, if a field `color` contains CSS color names, we can set `range` to `{field: \"color\"}`.\n\n__Notes:__\n\n1) For color scales you can also specify a color [`scheme`](https://vega.github.io/vega-lite/docs/scale.html#scheme) instead of `range`.\n\n2) Any directly specified `range` for `x` and `y` channels will be ignored. Range can be customized via the view's corresponding [size](https://vega.github.io/vega-lite/docs/size.html) (`width` and `height`)." @@ -21013,10 +20943,10 @@ "rangeMax": { "anyOf": [ { - "type": "number" - }, - { - "type": "string" + "type": [ + "number", + "string" + ] }, { "$ref": "#/definitions/ExprRef" @@ -21027,10 +20957,10 @@ "rangeMin": { "anyOf": [ { - "type": "number" - }, - { - "type": "string" + "type": [ + "number", + "string" + ] }, { "$ref": "#/definitions/ExprRef" @@ -21902,13 +21832,13 @@ "bin": { "anyOf": [ { - "type": "boolean" + "type": [ + "boolean", + "null" + ] }, { "$ref": "#/definitions/BinParams" - }, - { - "type": "null" } ], "description": "A flag for binning a `quantitative` field, [an object defining binning parameters](https://vega.github.io/vega-lite/docs/bin.html#params), or indicating that the data for `x` or `y` channel are binned before they are imported into Vega-Lite (`\"binned\"`).\n\n- If `true`, default [binning parameters](https://vega.github.io/vega-lite/docs/bin.html) will be applied.\n\n- If `\"binned\"`, this indicates that the data for the `x` (or `y`) channel are already binned. You can map the bin-start field to `x` (or `y`) and the bin-end field to `x2` (or `y2`). The scale and axis will be formatted similar to binning in Vega-Lite. To adjust the axis ticks based on the bin step, you can also set the axis's [`tickMinStep`](https://vega.github.io/vega-lite/docs/axis.html#ticks) property.\n\n__Default value:__ `false`\n\n__See also:__ [`bin`](https://vega.github.io/vega-lite/docs/bin.html) documentation." @@ -22061,13 +21991,13 @@ "bin": { "anyOf": [ { - "type": "boolean" + "type": [ + "boolean", + "null" + ] }, { "$ref": "#/definitions/BinParams" - }, - { - "type": "null" } ], "description": "A flag for binning a `quantitative` field, [an object defining binning parameters](https://vega.github.io/vega-lite/docs/bin.html#params), or indicating that the data for `x` or `y` channel are binned before they are imported into Vega-Lite (`\"binned\"`).\n\n- If `true`, default [binning parameters](https://vega.github.io/vega-lite/docs/bin.html) will be applied.\n\n- If `\"binned\"`, this indicates that the data for the `x` (or `y`) channel are already binned. You can map the bin-start field to `x` (or `y`) and the bin-end field to `x2` (or `y2`). The scale and axis will be formatted similar to binning in Vega-Lite. To adjust the axis ticks based on the bin step, you can also set the axis's [`tickMinStep`](https://vega.github.io/vega-lite/docs/axis.html#ticks) property.\n\n__Default value:__ `false`\n\n__See also:__ [`bin`](https://vega.github.io/vega-lite/docs/bin.html) documentation." @@ -22196,10 +22126,10 @@ "$ref": "#/definitions/Gradient" }, { - "type": "string" - }, - { - "type": "null" + "type": [ + "string", + "null" + ] }, { "$ref": "#/definitions/ExprRef" @@ -22226,7 +22156,10 @@ "bin": { "anyOf": [ { - "type": "boolean" + "type": [ + "boolean", + "null" + ] }, { "$ref": "#/definitions/BinParams" @@ -22234,9 +22167,6 @@ { "const": "binned", "type": "string" - }, - { - "type": "null" } ], "description": "A flag for binning a `quantitative` field, [an object defining binning parameters](https://vega.github.io/vega-lite/docs/bin.html#params), or indicating that the data for `x` or `y` channel are binned before they are imported into Vega-Lite (`\"binned\"`).\n\n- If `true`, default [binning parameters](https://vega.github.io/vega-lite/docs/bin.html) will be applied.\n\n- If `\"binned\"`, this indicates that the data for the `x` (or `y`) channel are already binned. You can map the bin-start field to `x` (or `y`) and the bin-end field to `x2` (or `y2`). The scale and axis will be formatted similar to binning in Vega-Lite. To adjust the axis ticks based on the bin step, you can also set the axis's [`tickMinStep`](https://vega.github.io/vega-lite/docs/axis.html#ticks) property.\n\n__Default value:__ `false`\n\n__See also:__ [`bin`](https://vega.github.io/vega-lite/docs/bin.html) documentation." @@ -22325,10 +22255,10 @@ "value": { "anyOf": [ { - "type": "string" - }, - { - "type": "null" + "type": [ + "string", + "null" + ] }, { "$ref": "#/definitions/ExprRef" @@ -22369,13 +22299,13 @@ "bin": { "anyOf": [ { - "type": "boolean" + "type": [ + "boolean", + "null" + ] }, { "$ref": "#/definitions/BinParams" - }, - { - "type": "null" } ], "description": "A flag for binning a `quantitative` field, [an object defining binning parameters](https://vega.github.io/vega-lite/docs/bin.html#params), or indicating that the data for `x` or `y` channel are binned before they are imported into Vega-Lite (`\"binned\"`).\n\n- If `true`, default [binning parameters](https://vega.github.io/vega-lite/docs/bin.html) will be applied.\n\n- If `\"binned\"`, this indicates that the data for the `x` (or `y`) channel are already binned. You can map the bin-start field to `x` (or `y`) and the bin-end field to `x2` (or `y2`). The scale and axis will be formatted similar to binning in Vega-Lite. To adjust the axis ticks based on the bin step, you can also set the axis's [`tickMinStep`](https://vega.github.io/vega-lite/docs/axis.html#ticks) property.\n\n__Default value:__ `false`\n\n__See also:__ [`bin`](https://vega.github.io/vega-lite/docs/bin.html) documentation." @@ -22504,10 +22434,10 @@ "$ref": "#/definitions/Gradient" }, { - "type": "string" - }, - { - "type": "null" + "type": [ + "string", + "null" + ] }, { "$ref": "#/definitions/ExprRef" @@ -22534,13 +22464,13 @@ "bin": { "anyOf": [ { - "type": "boolean" + "type": [ + "boolean", + "null" + ] }, { "$ref": "#/definitions/BinParams" - }, - { - "type": "null" } ], "description": "A flag for binning a `quantitative` field, [an object defining binning parameters](https://vega.github.io/vega-lite/docs/bin.html#params), or indicating that the data for `x` or `y` channel are binned before they are imported into Vega-Lite (`\"binned\"`).\n\n- If `true`, default [binning parameters](https://vega.github.io/vega-lite/docs/bin.html) will be applied.\n\n- If `\"binned\"`, this indicates that the data for the `x` (or `y`) channel are already binned. You can map the bin-start field to `x` (or `y`) and the bin-end field to `x2` (or `y2`). The scale and axis will be formatted similar to binning in Vega-Lite. To adjust the axis ticks based on the bin step, you can also set the axis's [`tickMinStep`](https://vega.github.io/vega-lite/docs/axis.html#ticks) property.\n\n__Default value:__ `false`\n\n__See also:__ [`bin`](https://vega.github.io/vega-lite/docs/bin.html) documentation." @@ -22693,7 +22623,10 @@ "bin": { "anyOf": [ { - "type": "boolean" + "type": [ + "boolean", + "null" + ] }, { "$ref": "#/definitions/BinParams" @@ -22701,9 +22634,6 @@ { "const": "binned", "type": "string" - }, - { - "type": "null" } ], "description": "A flag for binning a `quantitative` field, [an object defining binning parameters](https://vega.github.io/vega-lite/docs/bin.html#params), or indicating that the data for `x` or `y` channel are binned before they are imported into Vega-Lite (`\"binned\"`).\n\n- If `true`, default [binning parameters](https://vega.github.io/vega-lite/docs/bin.html) will be applied.\n\n- If `\"binned\"`, this indicates that the data for the `x` (or `y`) channel are already binned. You can map the bin-start field to `x` (or `y`) and the bin-end field to `x2` (or `y2`). The scale and axis will be formatted similar to binning in Vega-Lite. To adjust the axis ticks based on the bin step, you can also set the axis's [`tickMinStep`](https://vega.github.io/vega-lite/docs/axis.html#ticks) property.\n\n__Default value:__ `false`\n\n__See also:__ [`bin`](https://vega.github.io/vega-lite/docs/bin.html) documentation." @@ -22792,10 +22722,10 @@ "value": { "anyOf": [ { - "type": "string" - }, - { - "type": "null" + "type": [ + "string", + "null" + ] }, { "$ref": "#/definitions/ExprRef" @@ -22822,7 +22752,10 @@ "bin": { "anyOf": [ { - "type": "boolean" + "type": [ + "boolean", + "null" + ] }, { "$ref": "#/definitions/BinParams" @@ -22830,9 +22763,6 @@ { "const": "binned", "type": "string" - }, - { - "type": "null" } ], "description": "A flag for binning a `quantitative` field, [an object defining binning parameters](https://vega.github.io/vega-lite/docs/bin.html#params), or indicating that the data for `x` or `y` channel are binned before they are imported into Vega-Lite (`\"binned\"`).\n\n- If `true`, default [binning parameters](https://vega.github.io/vega-lite/docs/bin.html) will be applied.\n\n- If `\"binned\"`, this indicates that the data for the `x` (or `y`) channel are already binned. You can map the bin-start field to `x` (or `y`) and the bin-end field to `x2` (or `y2`). The scale and axis will be formatted similar to binning in Vega-Lite. To adjust the axis ticks based on the bin step, you can also set the axis's [`tickMinStep`](https://vega.github.io/vega-lite/docs/axis.html#ticks) property.\n\n__Default value:__ `false`\n\n__See also:__ [`bin`](https://vega.github.io/vega-lite/docs/bin.html) documentation." @@ -23212,13 +23142,13 @@ "bin": { "anyOf": [ { - "type": "boolean" + "type": [ + "boolean", + "null" + ] }, { "$ref": "#/definitions/BinParams" - }, - { - "type": "null" } ], "description": "A flag for binning a `quantitative` field, [an object defining binning parameters](https://vega.github.io/vega-lite/docs/bin.html#params), or indicating that the data for `x` or `y` channel are binned before they are imported into Vega-Lite (`\"binned\"`).\n\n- If `true`, default [binning parameters](https://vega.github.io/vega-lite/docs/bin.html) will be applied.\n\n- If `\"binned\"`, this indicates that the data for the `x` (or `y`) channel are already binned. You can map the bin-start field to `x` (or `y`) and the bin-end field to `x2` (or `y2`). The scale and axis will be formatted similar to binning in Vega-Lite. To adjust the axis ticks based on the bin step, you can also set the axis's [`tickMinStep`](https://vega.github.io/vega-lite/docs/axis.html#ticks) property.\n\n__Default value:__ `false`\n\n__See also:__ [`bin`](https://vega.github.io/vega-lite/docs/bin.html) documentation." @@ -23388,7 +23318,10 @@ "bin": { "anyOf": [ { - "type": "boolean" + "type": [ + "boolean", + "null" + ] }, { "$ref": "#/definitions/BinParams" @@ -23396,9 +23329,6 @@ { "const": "binned", "type": "string" - }, - { - "type": "null" } ], "description": "A flag for binning a `quantitative` field, [an object defining binning parameters](https://vega.github.io/vega-lite/docs/bin.html#params), or indicating that the data for `x` or `y` channel are binned before they are imported into Vega-Lite (`\"binned\"`).\n\n- If `true`, default [binning parameters](https://vega.github.io/vega-lite/docs/bin.html) will be applied.\n\n- If `\"binned\"`, this indicates that the data for the `x` (or `y`) channel are already binned. You can map the bin-start field to `x` (or `y`) and the bin-end field to `x2` (or `y2`). The scale and axis will be formatted similar to binning in Vega-Lite. To adjust the axis ticks based on the bin step, you can also set the axis's [`tickMinStep`](https://vega.github.io/vega-lite/docs/axis.html#ticks) property.\n\n__Default value:__ `false`\n\n__See also:__ [`bin`](https://vega.github.io/vega-lite/docs/bin.html) documentation." @@ -23445,10 +23375,10 @@ "$ref": "#/definitions/StackOffset" }, { - "type": "null" - }, - { - "type": "boolean" + "type": [ + "null", + "boolean" + ] } ], "description": "Type of stacking offset if the field should be stacked. `stack` is only applicable for `x`, `y`, `theta`, and `radius` channels with continuous domains. For example, `stack` of `y` can be used to customize stacking for a vertical bar chart.\n\n`stack` can be one of the following values:\n- `\"zero\"` or `true`: stacking with baseline offset at zero value of the scale (for creating typical stacked [bar](https://vega.github.io/vega-lite/docs/stack.html#bar) and [area](https://vega.github.io/vega-lite/docs/stack.html#area) chart).\n- `\"normalize\"` - stacking with normalized domain (for creating [normalized stacked bar and area charts](https://vega.github.io/vega-lite/docs/stack.html#normalized).
\n-`\"center\"` - stacking with center baseline (for [streamgraph](https://vega.github.io/vega-lite/docs/stack.html#streamgraph)).\n- `null` or `false` - No-stacking. This will produce layered [bar](https://vega.github.io/vega-lite/docs/stack.html#layered-bar-chart) and area chart.\n\n__Default value:__ `zero` for plots with all of the following conditions are true: (1) the mark is `bar`, `area`, or `arc`; (2) the stacked measure channel (x or y) has a linear scale; (3) At least one of non-position channels mapped to an unaggregated field that is different from x and y. Otherwise, `null` by default.\n\n__See also:__ [`stack`](https://vega.github.io/vega-lite/docs/stack.html) documentation." @@ -23612,13 +23542,13 @@ "bin": { "anyOf": [ { - "type": "boolean" + "type": [ + "boolean", + "null" + ] }, { "$ref": "#/definitions/BinParams" - }, - { - "type": "null" } ], "description": "A flag for binning a `quantitative` field, [an object defining binning parameters](https://vega.github.io/vega-lite/docs/bin.html#params), or indicating that the data for `x` or `y` channel are binned before they are imported into Vega-Lite (`\"binned\"`).\n\n- If `true`, default [binning parameters](https://vega.github.io/vega-lite/docs/bin.html) will be applied.\n\n- If `\"binned\"`, this indicates that the data for the `x` (or `y`) channel are already binned. You can map the bin-start field to `x` (or `y`) and the bin-end field to `x2` (or `y2`). The scale and axis will be formatted similar to binning in Vega-Lite. To adjust the axis ticks based on the bin step, you can also set the axis's [`tickMinStep`](https://vega.github.io/vega-lite/docs/axis.html#ticks) property.\n\n__Default value:__ `false`\n\n__See also:__ [`bin`](https://vega.github.io/vega-lite/docs/bin.html) documentation." @@ -23744,10 +23674,10 @@ "value": { "anyOf": [ { - "type": "string" - }, - { - "type": "null" + "type": [ + "string", + "null" + ] }, { "$ref": "#/definitions/ExprRef" @@ -23774,13 +23704,13 @@ "bin": { "anyOf": [ { - "type": "boolean" + "type": [ + "boolean", + "null" + ] }, { "$ref": "#/definitions/BinParams" - }, - { - "type": "null" } ], "description": "A flag for binning a `quantitative` field, [an object defining binning parameters](https://vega.github.io/vega-lite/docs/bin.html#params), or indicating that the data for `x` or `y` channel are binned before they are imported into Vega-Lite (`\"binned\"`).\n\n- If `true`, default [binning parameters](https://vega.github.io/vega-lite/docs/bin.html) will be applied.\n\n- If `\"binned\"`, this indicates that the data for the `x` (or `y`) channel are already binned. You can map the bin-start field to `x` (or `y`) and the bin-end field to `x2` (or `y2`). The scale and axis will be formatted similar to binning in Vega-Lite. To adjust the axis ticks based on the bin step, you can also set the axis's [`tickMinStep`](https://vega.github.io/vega-lite/docs/axis.html#ticks) property.\n\n__Default value:__ `false`\n\n__See also:__ [`bin`](https://vega.github.io/vega-lite/docs/bin.html) documentation." @@ -23933,13 +23863,13 @@ "bin": { "anyOf": [ { - "type": "boolean" + "type": [ + "boolean", + "null" + ] }, { "$ref": "#/definitions/BinParams" - }, - { - "type": "null" } ], "description": "A flag for binning a `quantitative` field, [an object defining binning parameters](https://vega.github.io/vega-lite/docs/bin.html#params), or indicating that the data for `x` or `y` channel are binned before they are imported into Vega-Lite (`\"binned\"`).\n\n- If `true`, default [binning parameters](https://vega.github.io/vega-lite/docs/bin.html) will be applied.\n\n- If `\"binned\"`, this indicates that the data for the `x` (or `y`) channel are already binned. You can map the bin-start field to `x` (or `y`) and the bin-end field to `x2` (or `y2`). The scale and axis will be formatted similar to binning in Vega-Lite. To adjust the axis ticks based on the bin step, you can also set the axis's [`tickMinStep`](https://vega.github.io/vega-lite/docs/axis.html#ticks) property.\n\n__Default value:__ `false`\n\n__See also:__ [`bin`](https://vega.github.io/vega-lite/docs/bin.html) documentation." @@ -24068,10 +23998,10 @@ "$ref": "#/definitions/Gradient" }, { - "type": "string" - }, - { - "type": "null" + "type": [ + "string", + "null" + ] }, { "$ref": "#/definitions/ExprRef" @@ -24098,13 +24028,13 @@ "bin": { "anyOf": [ { - "type": "boolean" + "type": [ + "boolean", + "null" + ] }, { "$ref": "#/definitions/BinParams" - }, - { - "type": "null" } ], "description": "A flag for binning a `quantitative` field, [an object defining binning parameters](https://vega.github.io/vega-lite/docs/bin.html#params), or indicating that the data for `x` or `y` channel are binned before they are imported into Vega-Lite (`\"binned\"`).\n\n- If `true`, default [binning parameters](https://vega.github.io/vega-lite/docs/bin.html) will be applied.\n\n- If `\"binned\"`, this indicates that the data for the `x` (or `y`) channel are already binned. You can map the bin-start field to `x` (or `y`) and the bin-end field to `x2` (or `y2`). The scale and axis will be formatted similar to binning in Vega-Lite. To adjust the axis ticks based on the bin step, you can also set the axis's [`tickMinStep`](https://vega.github.io/vega-lite/docs/axis.html#ticks) property.\n\n__Default value:__ `false`\n\n__See also:__ [`bin`](https://vega.github.io/vega-lite/docs/bin.html) documentation." @@ -24260,13 +24190,13 @@ "bin": { "anyOf": [ { - "type": "boolean" + "type": [ + "boolean", + "null" + ] }, { "$ref": "#/definitions/BinParams" - }, - { - "type": "null" } ], "description": "A flag for binning a `quantitative` field, [an object defining binning parameters](https://vega.github.io/vega-lite/docs/bin.html#params), or indicating that the data for `x` or `y` channel are binned before they are imported into Vega-Lite (`\"binned\"`).\n\n- If `true`, default [binning parameters](https://vega.github.io/vega-lite/docs/bin.html) will be applied.\n\n- If `\"binned\"`, this indicates that the data for the `x` (or `y`) channel are already binned. You can map the bin-start field to `x` (or `y`) and the bin-end field to `x2` (or `y2`). The scale and axis will be formatted similar to binning in Vega-Lite. To adjust the axis ticks based on the bin step, you can also set the axis's [`tickMinStep`](https://vega.github.io/vega-lite/docs/axis.html#ticks) property.\n\n__Default value:__ `false`\n\n__See also:__ [`bin`](https://vega.github.io/vega-lite/docs/bin.html) documentation." @@ -24419,13 +24349,13 @@ "bin": { "anyOf": [ { - "type": "boolean" + "type": [ + "boolean", + "null" + ] }, { "$ref": "#/definitions/BinParams" - }, - { - "type": "null" } ], "description": "A flag for binning a `quantitative` field, [an object defining binning parameters](https://vega.github.io/vega-lite/docs/bin.html#params), or indicating that the data for `x` or `y` channel are binned before they are imported into Vega-Lite (`\"binned\"`).\n\n- If `true`, default [binning parameters](https://vega.github.io/vega-lite/docs/bin.html) will be applied.\n\n- If `\"binned\"`, this indicates that the data for the `x` (or `y`) channel are already binned. You can map the bin-start field to `x` (or `y`) and the bin-end field to `x2` (or `y2`). The scale and axis will be formatted similar to binning in Vega-Lite. To adjust the axis ticks based on the bin step, you can also set the axis's [`tickMinStep`](https://vega.github.io/vega-lite/docs/axis.html#ticks) property.\n\n__Default value:__ `false`\n\n__See also:__ [`bin`](https://vega.github.io/vega-lite/docs/bin.html) documentation." @@ -24578,7 +24508,10 @@ "bin": { "anyOf": [ { - "type": "boolean" + "type": [ + "boolean", + "null" + ] }, { "$ref": "#/definitions/BinParams" @@ -24586,9 +24519,6 @@ { "const": "binned", "type": "string" - }, - { - "type": "null" } ], "description": "A flag for binning a `quantitative` field, [an object defining binning parameters](https://vega.github.io/vega-lite/docs/bin.html#params), or indicating that the data for `x` or `y` channel are binned before they are imported into Vega-Lite (`\"binned\"`).\n\n- If `true`, default [binning parameters](https://vega.github.io/vega-lite/docs/bin.html) will be applied.\n\n- If `\"binned\"`, this indicates that the data for the `x` (or `y`) channel are already binned. You can map the bin-start field to `x` (or `y`) and the bin-end field to `x2` (or `y2`). The scale and axis will be formatted similar to binning in Vega-Lite. To adjust the axis ticks based on the bin step, you can also set the axis's [`tickMinStep`](https://vega.github.io/vega-lite/docs/axis.html#ticks) property.\n\n__Default value:__ `false`\n\n__See also:__ [`bin`](https://vega.github.io/vega-lite/docs/bin.html) documentation." @@ -24730,7 +24660,10 @@ "bin": { "anyOf": [ { - "type": "boolean" + "type": [ + "boolean", + "null" + ] }, { "$ref": "#/definitions/BinParams" @@ -24738,9 +24671,6 @@ { "const": "binned", "type": "string" - }, - { - "type": "null" } ], "description": "A flag for binning a `quantitative` field, [an object defining binning parameters](https://vega.github.io/vega-lite/docs/bin.html#params), or indicating that the data for `x` or `y` channel are binned before they are imported into Vega-Lite (`\"binned\"`).\n\n- If `true`, default [binning parameters](https://vega.github.io/vega-lite/docs/bin.html) will be applied.\n\n- If `\"binned\"`, this indicates that the data for the `x` (or `y`) channel are already binned. You can map the bin-start field to `x` (or `y`) and the bin-end field to `x2` (or `y2`). The scale and axis will be formatted similar to binning in Vega-Lite. To adjust the axis ticks based on the bin step, you can also set the axis's [`tickMinStep`](https://vega.github.io/vega-lite/docs/axis.html#ticks) property.\n\n__Default value:__ `false`\n\n__See also:__ [`bin`](https://vega.github.io/vega-lite/docs/bin.html) documentation." @@ -24787,10 +24717,10 @@ "$ref": "#/definitions/StackOffset" }, { - "type": "null" - }, - { - "type": "boolean" + "type": [ + "null", + "boolean" + ] } ], "description": "Type of stacking offset if the field should be stacked. `stack` is only applicable for `x`, `y`, `theta`, and `radius` channels with continuous domains. For example, `stack` of `y` can be used to customize stacking for a vertical bar chart.\n\n`stack` can be one of the following values:\n- `\"zero\"` or `true`: stacking with baseline offset at zero value of the scale (for creating typical stacked [bar](https://vega.github.io/vega-lite/docs/stack.html#bar) and [area](https://vega.github.io/vega-lite/docs/stack.html#area) chart).\n- `\"normalize\"` - stacking with normalized domain (for creating [normalized stacked bar and area charts](https://vega.github.io/vega-lite/docs/stack.html#normalized).
\n-`\"center\"` - stacking with center baseline (for [streamgraph](https://vega.github.io/vega-lite/docs/stack.html#streamgraph)).\n- `null` or `false` - No-stacking. This will produce layered [bar](https://vega.github.io/vega-lite/docs/stack.html#layered-bar-chart) and area chart.\n\n__Default value:__ `zero` for plots with all of the following conditions are true: (1) the mark is `bar`, `area`, or `arc`; (2) the stacked measure channel (x or y) has a linear scale; (3) At least one of non-position channels mapped to an unaggregated field that is different from x and y. Otherwise, `null` by default.\n\n__See also:__ [`stack`](https://vega.github.io/vega-lite/docs/stack.html) documentation." @@ -24950,10 +24880,10 @@ "items": { "$ref": "#/definitions/StringFieldDef" }, - "type": "array" - }, - { - "type": "null" + "type": [ + "array", + "null" + ] } ], "description": "The tooltip text to show upon mouse hover. Specifying `tooltip` encoding overrides [the `tooltip` property in the mark definition](https://vega.github.io/vega-lite/docs/mark.html#mark-def).\n\nSee the [`tooltip`](https://vega.github.io/vega-lite/docs/tooltip.html) documentation for a detailed discussion about tooltip in Vega-Lite." @@ -24974,7 +24904,10 @@ "bin": { "anyOf": [ { - "type": "boolean" + "type": [ + "boolean", + "null" + ] }, { "$ref": "#/definitions/BinParams" @@ -24982,9 +24915,6 @@ { "const": "binned", "type": "string" - }, - { - "type": "null" } ], "description": "A flag for binning a `quantitative` field, [an object defining binning parameters](https://vega.github.io/vega-lite/docs/bin.html#params), or indicating that the data for `x` or `y` channel are binned before they are imported into Vega-Lite (`\"binned\"`).\n\n- If `true`, default [binning parameters](https://vega.github.io/vega-lite/docs/bin.html) will be applied.\n\n- If `\"binned\"`, this indicates that the data for the `x` (or `y`) channel are already binned. You can map the bin-start field to `x` (or `y`) and the bin-end field to `x2` (or `y2`). The scale and axis will be formatted similar to binning in Vega-Lite. To adjust the axis ticks based on the bin step, you can also set the axis's [`tickMinStep`](https://vega.github.io/vega-lite/docs/axis.html#ticks) property.\n\n__Default value:__ `false`\n\n__See also:__ [`bin`](https://vega.github.io/vega-lite/docs/bin.html) documentation." @@ -25073,10 +25003,10 @@ "value": { "anyOf": [ { - "type": "string" - }, - { - "type": "null" + "type": [ + "string", + "null" + ] }, { "$ref": "#/definitions/ExprRef" @@ -25114,7 +25044,10 @@ "bin": { "anyOf": [ { - "type": "boolean" + "type": [ + "boolean", + "null" + ] }, { "$ref": "#/definitions/BinParams" @@ -25122,9 +25055,6 @@ { "const": "binned", "type": "string" - }, - { - "type": "null" } ], "description": "A flag for binning a `quantitative` field, [an object defining binning parameters](https://vega.github.io/vega-lite/docs/bin.html#params), or indicating that the data for `x` or `y` channel are binned before they are imported into Vega-Lite (`\"binned\"`).\n\n- If `true`, default [binning parameters](https://vega.github.io/vega-lite/docs/bin.html) will be applied.\n\n- If `\"binned\"`, this indicates that the data for the `x` (or `y`) channel are already binned. You can map the bin-start field to `x` (or `y`) and the bin-end field to `x2` (or `y2`). The scale and axis will be formatted similar to binning in Vega-Lite. To adjust the axis ticks based on the bin step, you can also set the axis's [`tickMinStep`](https://vega.github.io/vega-lite/docs/axis.html#ticks) property.\n\n__Default value:__ `false`\n\n__See also:__ [`bin`](https://vega.github.io/vega-lite/docs/bin.html) documentation." @@ -25182,10 +25112,10 @@ "$ref": "#/definitions/StackOffset" }, { - "type": "null" - }, - { - "type": "boolean" + "type": [ + "null", + "boolean" + ] } ], "description": "Type of stacking offset if the field should be stacked. `stack` is only applicable for `x`, `y`, `theta`, and `radius` channels with continuous domains. For example, `stack` of `y` can be used to customize stacking for a vertical bar chart.\n\n`stack` can be one of the following values:\n- `\"zero\"` or `true`: stacking with baseline offset at zero value of the scale (for creating typical stacked [bar](https://vega.github.io/vega-lite/docs/stack.html#bar) and [area](https://vega.github.io/vega-lite/docs/stack.html#area) chart).\n- `\"normalize\"` - stacking with normalized domain (for creating [normalized stacked bar and area charts](https://vega.github.io/vega-lite/docs/stack.html#normalized).
\n-`\"center\"` - stacking with center baseline (for [streamgraph](https://vega.github.io/vega-lite/docs/stack.html#streamgraph)).\n- `null` or `false` - No-stacking. This will produce layered [bar](https://vega.github.io/vega-lite/docs/stack.html#layered-bar-chart) and area chart.\n\n__Default value:__ `zero` for plots with all of the following conditions are true: (1) the mark is `bar`, `area`, or `arc`; (2) the stacked measure channel (x or y) has a linear scale; (3) At least one of non-position channels mapped to an unaggregated field that is different from x and y. Otherwise, `null` by default.\n\n__See also:__ [`stack`](https://vega.github.io/vega-lite/docs/stack.html) documentation." @@ -25460,7 +25390,10 @@ "bin": { "anyOf": [ { - "type": "boolean" + "type": [ + "boolean", + "null" + ] }, { "$ref": "#/definitions/BinParams" @@ -25468,9 +25401,6 @@ { "const": "binned", "type": "string" - }, - { - "type": "null" } ], "description": "A flag for binning a `quantitative` field, [an object defining binning parameters](https://vega.github.io/vega-lite/docs/bin.html#params), or indicating that the data for `x` or `y` channel are binned before they are imported into Vega-Lite (`\"binned\"`).\n\n- If `true`, default [binning parameters](https://vega.github.io/vega-lite/docs/bin.html) will be applied.\n\n- If `\"binned\"`, this indicates that the data for the `x` (or `y`) channel are already binned. You can map the bin-start field to `x` (or `y`) and the bin-end field to `x2` (or `y2`). The scale and axis will be formatted similar to binning in Vega-Lite. To adjust the axis ticks based on the bin step, you can also set the axis's [`tickMinStep`](https://vega.github.io/vega-lite/docs/axis.html#ticks) property.\n\n__Default value:__ `false`\n\n__See also:__ [`bin`](https://vega.github.io/vega-lite/docs/bin.html) documentation." @@ -25528,10 +25458,10 @@ "$ref": "#/definitions/StackOffset" }, { - "type": "null" - }, - { - "type": "boolean" + "type": [ + "null", + "boolean" + ] } ], "description": "Type of stacking offset if the field should be stacked. `stack` is only applicable for `x`, `y`, `theta`, and `radius` channels with continuous domains. For example, `stack` of `y` can be used to customize stacking for a vertical bar chart.\n\n`stack` can be one of the following values:\n- `\"zero\"` or `true`: stacking with baseline offset at zero value of the scale (for creating typical stacked [bar](https://vega.github.io/vega-lite/docs/stack.html#bar) and [area](https://vega.github.io/vega-lite/docs/stack.html#area) chart).\n- `\"normalize\"` - stacking with normalized domain (for creating [normalized stacked bar and area charts](https://vega.github.io/vega-lite/docs/stack.html#normalized).
\n-`\"center\"` - stacking with center baseline (for [streamgraph](https://vega.github.io/vega-lite/docs/stack.html#streamgraph)).\n- `null` or `false` - No-stacking. This will produce layered [bar](https://vega.github.io/vega-lite/docs/stack.html#layered-bar-chart) and area chart.\n\n__Default value:__ `zero` for plots with all of the following conditions are true: (1) the mark is `bar`, `area`, or `arc`; (2) the stacked measure channel (x or y) has a linear scale; (3) At least one of non-position channels mapped to an unaggregated field that is different from x and y. Otherwise, `null` by default.\n\n__See also:__ [`stack`](https://vega.github.io/vega-lite/docs/stack.html) documentation." @@ -26101,7 +26031,10 @@ "bin": { "anyOf": [ { - "type": "boolean" + "type": [ + "boolean", + "null" + ] }, { "$ref": "#/definitions/BinParams" @@ -26109,9 +26042,6 @@ { "const": "binned", "type": "string" - }, - { - "type": "null" } ], "description": "A flag for binning a `quantitative` field, [an object defining binning parameters](https://vega.github.io/vega-lite/docs/bin.html#params), or indicating that the data for `x` or `y` channel are binned before they are imported into Vega-Lite (`\"binned\"`).\n\n- If `true`, default [binning parameters](https://vega.github.io/vega-lite/docs/bin.html) will be applied.\n\n- If `\"binned\"`, this indicates that the data for the `x` (or `y`) channel are already binned. You can map the bin-start field to `x` (or `y`) and the bin-end field to `x2` (or `y2`). The scale and axis will be formatted similar to binning in Vega-Lite. To adjust the axis ticks based on the bin step, you can also set the axis's [`tickMinStep`](https://vega.github.io/vega-lite/docs/axis.html#ticks) property.\n\n__Default value:__ `false`\n\n__See also:__ [`bin`](https://vega.github.io/vega-lite/docs/bin.html) documentation." @@ -26281,16 +26211,12 @@ "type": "string" }, "Text": { - "anyOf": [ - { - "type": "string" - }, - { - "items": { - "type": "string" - }, - "type": "array" - } + "items": { + "type": "string" + }, + "type": [ + "string", + "array" ] }, "TextBaseline": { @@ -27036,22 +26962,18 @@ "tooltip": { "anyOf": [ { - "type": "number" - }, - { - "type": "string" - }, - { - "type": "boolean" + "type": [ + "number", + "string", + "boolean", + "null" + ] }, { "$ref": "#/definitions/TooltipContent" }, { "$ref": "#/definitions/ExprRef" - }, - { - "type": "null" } ], "description": "The tooltip text string to show upon mouse hover or an object defining which fields should the tooltip be derived from.\n\n- If `tooltip` is `true` or `{\"content\": \"encoding\"}`, then all fields from `encoding` will be used.\n- If `tooltip` is `{\"content\": \"data\"}`, then all fields that appear in the highlighted data point will be used.\n- If set to `null` or `false`, then no tooltip will be used.\n\nSee the [`tooltip`](https://vega.github.io/vega-lite/docs/tooltip.html) documentation for a detailed discussion about tooltip in Vega-Lite.\n\n__Default value:__ `null`" @@ -27463,18 +27385,14 @@ ] }, "style": { - "anyOf": [ - { - "type": "string" - }, - { - "items": { - "type": "string" - }, - "type": "array" - } - ], - "description": "A [mark style property](https://vega.github.io/vega-lite/docs/config.html#style) to apply to the title text mark.\n\n__Default value:__ `\"group-title\"`." + "description": "A [mark style property](https://vega.github.io/vega-lite/docs/config.html#style) to apply to the title text mark.\n\n__Default value:__ `\"group-title\"`.", + "items": { + "type": "string" + }, + "type": [ + "string", + "array" + ] }, "subtitle": { "$ref": "#/definitions/Text", @@ -28815,16 +28733,12 @@ "views": { "description": "By default, top-level selections are applied to every view in the visualization. If this property is specified, selections will only be applied to views with the given names.", "items": { - "anyOf": [ - { - "type": "string" - }, - { - "items": { - "type": "string" - }, - "type": "array" - } + "items": { + "type": "string" + }, + "type": [ + "string", + "array" ] }, "type": "array" @@ -29188,7 +29102,10 @@ "bin": { "anyOf": [ { - "type": "boolean" + "type": [ + "boolean", + "null" + ] }, { "$ref": "#/definitions/BinParams" @@ -29196,9 +29113,6 @@ { "const": "binned", "type": "string" - }, - { - "type": "null" } ], "description": "A flag for binning a `quantitative` field, [an object defining binning parameters](https://vega.github.io/vega-lite/docs/bin.html#params), or indicating that the data for `x` or `y` channel are binned before they are imported into Vega-Lite (`\"binned\"`).\n\n- If `true`, default [binning parameters](https://vega.github.io/vega-lite/docs/bin.html) will be applied.\n\n- If `\"binned\"`, this indicates that the data for the `x` (or `y`) channel are already binned. You can map the bin-start field to `x` (or `y`) and the bin-end field to `x2` (or `y2`). The scale and axis will be formatted similar to binning in Vega-Lite. To adjust the axis ticks based on the bin step, you can also set the axis's [`tickMinStep`](https://vega.github.io/vega-lite/docs/axis.html#ticks) property.\n\n__Default value:__ `false`\n\n__See also:__ [`bin`](https://vega.github.io/vega-lite/docs/bin.html) documentation." @@ -29491,10 +29405,10 @@ "$ref": "#/definitions/Gradient" }, { - "type": "string" - }, - { - "type": "null" + "type": [ + "string", + "null" + ] }, { "$ref": "#/definitions/ExprRef" @@ -29529,10 +29443,10 @@ "value": { "anyOf": [ { - "type": "string" - }, - { - "type": "null" + "type": [ + "string", + "null" + ] }, { "$ref": "#/definitions/ExprRef" @@ -29640,10 +29554,10 @@ "value": { "anyOf": [ { - "type": "string" - }, - { - "type": "null" + "type": [ + "string", + "null" + ] }, { "$ref": "#/definitions/ExprRef" @@ -29917,18 +29831,14 @@ ] }, "style": { - "anyOf": [ - { - "type": "string" - }, - { - "items": { - "type": "string" - }, - "type": "array" - } - ], - "description": "A string or array of strings indicating the name of custom styles to apply to the view background. A style is a named collection of mark property defaults defined within the [style configuration](https://vega.github.io/vega-lite/docs/mark.html#style-config). If style is an array, later styles will override earlier styles.\n\n__Default value:__ `\"cell\"` __Note:__ Any specified view background properties will augment the default style." + "description": "A string or array of strings indicating the name of custom styles to apply to the view background. A style is a named collection of mark property defaults defined within the [style configuration](https://vega.github.io/vega-lite/docs/mark.html#style-config). If style is an array, later styles will override earlier styles.\n\n__Default value:__ `\"cell\"` __Note:__ Any specified view background properties will augment the default style.", + "items": { + "type": "string" + }, + "type": [ + "string", + "array" + ] } }, "type": "object" @@ -29964,44 +29874,36 @@ "description": "The mouse cursor used over the view. Any valid [CSS cursor type](https://developer.mozilla.org/en-US/docs/Web/CSS/cursor#Values) can be used." }, "discreteHeight": { - "anyOf": [ - { + "additionalProperties": false, + "description": "The default height when the plot has non arc marks and either a discrete y-field or no y-field. The height can be either a number indicating a fixed height or an object in the form of `{step: number}` defining the height per discrete step.\n\n__Default value:__ a step size based on `config.view.step`.", + "properties": { + "step": { "type": "number" - }, - { - "additionalProperties": false, - "properties": { - "step": { - "type": "number" - } - }, - "required": [ - "step" - ], - "type": "object" } + }, + "required": [ + "step" ], - "description": "The default height when the plot has non arc marks and either a discrete y-field or no y-field. The height can be either a number indicating a fixed height or an object in the form of `{step: number}` defining the height per discrete step.\n\n__Default value:__ a step size based on `config.view.step`." + "type": [ + "number", + "object" + ] }, "discreteWidth": { - "anyOf": [ - { + "additionalProperties": false, + "description": "The default width when the plot has non-arc marks and either a discrete x-field or no x-field. The width can be either a number indicating a fixed width or an object in the form of `{step: number}` defining the width per discrete step.\n\n__Default value:__ a step size based on `config.view.step`.", + "properties": { + "step": { "type": "number" - }, - { - "additionalProperties": false, - "properties": { - "step": { - "type": "number" - } - }, - "required": [ - "step" - ], - "type": "object" } + }, + "required": [ + "step" ], - "description": "The default width when the plot has non-arc marks and either a discrete x-field or no x-field. The width can be either a number indicating a fixed width or an object in the form of `{step: number}` defining the width per discrete step.\n\n__Default value:__ a step size based on `config.view.step`." + "type": [ + "number", + "object" + ] }, "fill": { "anyOf": [ From 291ebf909b2481d1f2be94142497e5afb108c105 Mon Sep 17 00:00:00 2001 From: Danielle Church Date: Sat, 27 Mar 2021 16:33:15 -0400 Subject: [PATCH 4/7] Simplifying logic in addReachable, adding tests --- src/Utils/removeUnreachable.ts | 69 ++++++++++++----------------- test/unit/removeUnreachable.test.ts | 60 +++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 41 deletions(-) create mode 100644 test/unit/removeUnreachable.test.ts diff --git a/src/Utils/removeUnreachable.ts b/src/Utils/removeUnreachable.ts index 8619e5638..cfbd3efaa 100644 --- a/src/Utils/removeUnreachable.ts +++ b/src/Utils/removeUnreachable.ts @@ -1,14 +1,24 @@ import { JSONSchema7Definition } from "json-schema"; -import { isArray, isBoolean } from "util"; import { Definition } from "./../Schema/Definition"; import { StringMap } from "./StringMap"; function addReachable( - definition: Definition | JSONSchema7Definition, + definition: Definition | JSONSchema7Definition | undefined | Array, definitions: StringMap, reachable: Set ) { - if (isBoolean(definition)) { + function addReachableProperties(properties: Record | undefined) { + for (const def of Object.values(properties || {})) { + addReachable(def, definitions, reachable); + } + } + if (!definition || typeof definition !== "object") { + return; + } + if (Array.isArray(definition)) { + for (const def of definition) { + addReachable(def, definitions, reachable); + } return; } @@ -21,45 +31,22 @@ function addReachable( reachable.add(typeName); addReachable(definitions[typeName], definitions, reachable); } else { - if (definition.anyOf) { - for (const def of definition.anyOf) { - addReachable(def, definitions, reachable); - } - } - if (definition.allOf) { - for (const def of definition.allOf) { - addReachable(def, definitions, reachable); - } - } - if (definition.oneOf) { - for (const def of definition.oneOf) { - addReachable(def, definitions, reachable); - } - } - if (definition.not) { - addReachable(definition.not, definitions, reachable); - } - if (definition.type === "object" || (Array.isArray(definition.type) && definition.type.includes("object"))) { - for (const prop in definition.properties || {}) { - const propDefinition = definition.properties![prop]; - addReachable(propDefinition, definitions, reachable); - } + addReachable(definition.anyOf, definitions, reachable); + addReachable(definition.allOf, definitions, reachable); + addReachable(definition.oneOf, definitions, reachable); + addReachable(definition.not, definitions, reachable); + addReachable(definition.contains, definitions, reachable); - const additionalProperties = definition.additionalProperties; - if (additionalProperties) { - addReachable(additionalProperties, definitions, reachable); - } - } - if (definition.type === "array" || (Array.isArray(definition.type) && definition.type.includes("array"))) { - const items = definition.items; - if (isArray(items)) { - for (const item of items) { - addReachable(item, definitions, reachable); - } - } else if (items) { - addReachable(items, definitions, reachable); - } - } + addReachable(definition.if, definitions, reachable); + addReachable(definition.then, definitions, reachable); + addReachable(definition.else, definitions, reachable); + + addReachableProperties(definition.properties); + addReachableProperties(definition.patternProperties); + addReachable(definition.additionalProperties, definitions, reachable); + + addReachable(definition.items, definitions, reachable); + addReachable(definition.additionalItems, definitions, reachable); } } diff --git a/test/unit/removeUnreachable.test.ts b/test/unit/removeUnreachable.test.ts new file mode 100644 index 000000000..80d1feef2 --- /dev/null +++ b/test/unit/removeUnreachable.test.ts @@ -0,0 +1,60 @@ +import { Definition } from "../../src/Schema/Definition"; +import { removeUnreachable } from "../../src/Utils/removeUnreachable"; +import { StringMap } from "../../src/Utils/StringMap"; + +type DefinitionGenerator = (makeRef: (reffedDefinition?: Definition) => Definition) => Definition; +function packDefinition(defGen: DefinitionGenerator): [root: Definition, definitions: StringMap] { + const definitions: StringMap = {}; + let refIdx = 0; + function makeRefFunc(reffedDefinition: Definition = {}): Definition { + const defName = `packedDef${refIdx++}`; + definitions[defName] = reffedDefinition; + return { $ref: `#/definitions/${defName}` }; + } + const root = defGen(makeRefFunc); + return [root, definitions]; +} + +function assertReachableDefinitions(defGen: DefinitionGenerator) { + return (): void => { + const [rootTypeDefinition, definitions] = packDefinition(defGen); + let prunedDefinitions = removeUnreachable(rootTypeDefinition, definitions); + expect(prunedDefinitions).toEqual(definitions); + + definitions["extraDef"] = {}; + prunedDefinitions = removeUnreachable(rootTypeDefinition, definitions); + expect(prunedDefinitions).not.toEqual(definitions); + }; +} + +describe("removeUnreachable", () => { + it( + "preserves reachability for a packed definition", + assertReachableDefinitions((makeRef) => ({ + type: ["array", "boolean", "integer", "null", "number", "object", "string"], + allOf: [makeRef(), makeRef()], + anyOf: [makeRef(), makeRef()], + oneOf: [makeRef(), makeRef()], + contains: makeRef(), + not: makeRef(), + if: makeRef(), + then: makeRef(), + else: makeRef(), + properties: { foo: makeRef(), bar: makeRef(), $ref: makeRef() }, + patternProperties: { "^foo$": makeRef(), "b.a.r": makeRef(), $ref: makeRef() }, + additionalProperties: makeRef(), + items: [makeRef(), makeRef()], + additionalItems: makeRef(), + })) + ); + + it( + "preserves reachability for homogenous arrays", + assertReachableDefinitions((makeRef) => ({ type: "array", items: makeRef() })) + ); + + it( + "preserves reachability for indirect refs", + assertReachableDefinitions((makeRef) => ({ not: makeRef({ not: makeRef() }) })) + ); +}); From 30c22f70e7e2ef5cee6276a9f6db30cac8c1e2e7 Mon Sep 17 00:00:00 2001 From: Danielle Church Date: Sat, 27 Mar 2021 17:29:26 -0400 Subject: [PATCH 5/7] Adding functional tests, mergeDefinitions tests In addition to the raw schema tests which just check that nothing has changed since the last snapshot, this adds makeExemplar, which builds a raw value using the parsed BaseType representation of a TypeScript type tree. This way, tests can specify valid/invalid data to be verified using the Ajv validator in addition to raw schema output tests. --- src/Utils/makeExemplar.ts | 230 +++++++++++++++++++ test/utils.ts | 120 +++++++++- test/valid-data-type.test.ts | 9 +- test/valid-data/type-union-weird/main.ts | 72 ++++++ test/valid-data/type-union-weird/schema.json | 112 +++++++++ test/vega-lite.test.ts | 4 +- 6 files changed, 537 insertions(+), 10 deletions(-) create mode 100644 src/Utils/makeExemplar.ts create mode 100644 test/valid-data/type-union-weird/main.ts create mode 100644 test/valid-data/type-union-weird/schema.json diff --git a/src/Utils/makeExemplar.ts b/src/Utils/makeExemplar.ts new file mode 100644 index 000000000..0789b1425 --- /dev/null +++ b/src/Utils/makeExemplar.ts @@ -0,0 +1,230 @@ +import { AliasType } from "../Type/AliasType"; +import { AnnotatedType } from "../Type/AnnotatedType"; +import { ArrayType } from "../Type/ArrayType"; +import { BaseType } from "../Type/BaseType"; +import { BooleanType } from "../Type/BooleanType"; +import { DefinitionType } from "../Type/DefinitionType"; +import { EnumType } from "../Type/EnumType"; +import { IntersectionType } from "../Type/IntersectionType"; +import { LiteralType } from "../Type/LiteralType"; +import { NullType } from "../Type/NullType"; +import { NumberType } from "../Type/NumberType"; +import { ObjectType } from "../Type/ObjectType"; +import { OptionalType } from "../Type/OptionalType"; +import { ReferenceType } from "../Type/ReferenceType"; +import { RestType } from "../Type/RestType"; +import { StringType } from "../Type/StringType"; +import { SymbolType } from "../Type/SymbolType"; +import { TupleType } from "../Type/TupleType"; +import { UndefinedType } from "../Type/UndefinedType"; +import { UnionType } from "../Type/UnionType"; + +export function makeExemplar(type: BaseType | undefined): unknown { + return makeExemplars(type)[0]; +} + +export function makeExemplars(type: BaseType | undefined): readonly unknown[] { + while (type) { + if ( + type instanceof AliasType || + type instanceof AnnotatedType || + type instanceof DefinitionType || + type instanceof ReferenceType + ) { + type = type.getType(); + } else if (type instanceof ArrayType) { + const itemExemplars = makeExemplars(type.getItem()); + return [[], itemExemplars].concat(itemExemplars.map((e) => [e, e])); + } else if (type instanceof BooleanType) { + return [true, false]; + } else if (type instanceof EnumType) { + return type.getValues(); + } else if (type instanceof IntersectionType) { + return makeIntersectionExemplars(type); + } else if (type instanceof LiteralType) { + return [type.getValue()]; + } else if (type instanceof NullType) { + return [null]; + } else if (type instanceof NumberType) { + return [0, 1, -1]; + } else if (type instanceof ObjectType) { + return makeObjectExemplars(type); + } else if (type instanceof OptionalType) { + return makeExemplars(type.getType()).concat([undefined]); + } else if (type instanceof RestType) { + const exemplar = makeExemplars(type.getType()); + return [[], [exemplar], [exemplar, exemplar]]; + } else if (type instanceof StringType) { + return ["", "lorem ipsum"]; + } else if (type instanceof SymbolType) { + return [Symbol(), Symbol()]; + } else if (type instanceof TupleType) { + return makeTupleExemplars(type); + } else if (type instanceof UndefinedType) { + return [undefined]; + } else if (type instanceof UnionType) { + return type + .getTypes() + .map((t) => makeExemplars(t)) + .reduce((list, choice) => list.concat(choice), []); + } else { + throw new Error(`Can't make exemplar from type ${type.constructor.name}: ${type}`); + } + } + return [undefined]; +} + +type UnknownObject = Record; + +function makeIntersectionExemplars(type: IntersectionType): unknown[] { + const warnings: string[] = []; + function intersectExemplars( + exemplars: (readonly unknown[])[], + currentResult: unknown, + members: UnknownObject[] + ): unknown[] { + for (let i = 0; i < exemplars.length; i++) { + const choices = exemplars[i]; + if (choices.length > 1) { + return choices + .map((choice) => { + const subExemplars = exemplars.slice(i); // including the one with the multiple-choice element + subExemplars[0] = [choice]; // ...and overwriting it with a single choice + const subMembers = members.slice(); + return intersectExemplars(subExemplars, currentResult, subMembers); + }) + .reduce((list, choice) => list.concat(choice), []); + } else if (choices.length === 0) { + return []; + } + const exemplar = choices[0]; + if (exemplar == null) { + warnings.push(`Can't make exemplar from intersection with null/undefined`); + return []; + } else if (exemplar && typeof exemplar === "object" && !Array.isArray(exemplar)) { + members.push(exemplar as UnknownObject); + } else { + // We can only have one non-object member. It will become the base we add all the others to. + if (currentResult !== undefined && exemplar !== currentResult) { + warnings.push(`Can't make exemplar from complex intersection`); + return []; + } else { + currentResult = exemplar; + } + } + } + + // We've gotten here, which means we have exactly one choice at this level of recursion. + // Now we just need to merge the intersection members. + + if (members.length === 0) { + // no properties to add, just return the base value + return [currentResult]; + } + let result: UnknownObject; + if (currentResult === undefined) { + result = {}; + } else if (typeof currentResult !== "object") { + // for primitive values, box them to allow adding properties + result = new (currentResult as any).constructor(currentResult); + } else if (Array.isArray(currentResult)) { + result = currentResult.slice() as any; + } else { + result = Object.assign({}, currentResult); + } + + const collisions: Record = {}; + + for (const member of members) { + for (const [key, value] of Object.entries(member)) { + if (Object.getOwnPropertyDescriptor(result, key)) { + if (!(key in collisions)) { + collisions[key] = [result[key]]; + } + collisions[key].push(value); + } else { + result[key] = value; + } + } + } + + return resolveObjectChoices(result, Object.entries(collisions)); + } + + const choices = intersectExemplars( + type.getTypes().map((t) => makeExemplars(t)), + undefined, + [] + ); + if (choices.length === 0) { + throw new Error(`Could not make intersection; warnings=${JSON.stringify(warnings)}`); + } + return choices; +} + +function resolveObjectChoices(exemplar: UnknownObject, choiceEntries: [string, readonly unknown[]][]): UnknownObject[] { + if (choiceEntries.length === 0) { + return [exemplar]; + } + const [prop, choices] = choiceEntries[0]; + const results: UnknownObject[] = []; + for (const choice of choices) { + const newExemplar = new (exemplar.constructor as new (val: UnknownObject) => UnknownObject)(exemplar); + newExemplar[prop] = choice; + results.push(...resolveObjectChoices(exemplar, choiceEntries.slice(1))); + } + return results; +} + +function makeObjectExemplars(type: ObjectType): readonly unknown[] { + const fullObject: UnknownObject = {}; + const emptyObject: UnknownObject = {}; + const choices: [string, readonly unknown[]][] = []; + let hasOptional = false; + for (const prop of type.getProperties()) { + const name = prop.getName(); + const values = makeExemplars(prop.getType()); + if (values.length === 0) { + throw new Error(`Cannot make object exemplar with invalid property for type ${type}`); + } else if (values.length > 1) { + choices.push([name, values]); + } + const value = values[0]; + fullObject[name] = value; + if (prop.isRequired()) { + emptyObject[name] = value; + } else { + hasOptional = true; + } + } + const additional = type.getAdditionalProperties(); + if (additional === true) { + hasOptional = true; + fullObject[""] = "UNLIKELY VALUE"; + } else if (additional) { + hasOptional = true; + choices.push(["", makeExemplars(additional)]); + } + const allChoices = resolveObjectChoices(fullObject, choices); + if (hasOptional) { + allChoices.push(emptyObject); + } + return allChoices; +} + +function makeTupleExemplars(type: TupleType): readonly unknown[] { + const exemplars = type.getTypes().map((t) => makeExemplars(t)); + function makeTuples(prefix: readonly unknown[], items: (readonly unknown[])[]): (readonly unknown[])[] { + if (items.length === 0) { + return [prefix]; + } + const [head, ...tail] = items; + const results: (readonly unknown[])[] = []; + for (const choice of head) { + results.push(...makeTuples(prefix.concat([choice]), tail)); + } + return results; + } + + return makeTuples([], exemplars); +} diff --git a/test/utils.ts b/test/utils.ts index 5c0a41ac4..b1e52b028 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -8,16 +8,88 @@ import { createFormatter } from "../factory/formatter"; import { createParser } from "../factory/parser"; import { createProgram } from "../factory/program"; import { Config } from "../src/Config"; +import { Context } from "../src/NodeParser"; import { SchemaGenerator } from "../src/SchemaGenerator"; +import { makeExemplars } from "../src/Utils/makeExemplar"; +import { inspect } from "util"; +import { BaseType } from "../src/Type/BaseType"; const validator = new Ajv(); addFormats(validator); const basePath = "test/valid-data"; -export function createGenerator(config: Config): SchemaGenerator { - const program: ts.Program = createProgram(config); - return new SchemaGenerator(program, createParser(program, config), createFormatter(config), config); +interface Validator { + (value: T): boolean | Promise; + // ^^^^^^^^^^^^^^^^ *Why* does validateSchema() sometimes return this? Who knows! >.< +} +interface ErrorContainer { + errors?: { message?: string }[] | null; +} + +expect.extend({ + toValidateUsing( + this: jest.MatcherContext, + received: T, + validationFunction: Validator, + errorContainer?: ErrorContainer + ) { + errorContainer ??= validationFunction as ErrorContainer; + const previousErrors = errorContainer.errors; + errorContainer.errors = null; + try { + const result = validationFunction.call(errorContainer, received); + const errors = errorContainer.errors; + const pass = !!result && errors === null; + const options: jest.MatcherHintOptions = { + comment: "Schema validation", + isNot: this.isNot, + promise: this.promise, + }; + const hint = () => this.utils.matcherHint("toValidateUsing", undefined, "validator", options) + "\n\n"; + const printReceived = () => "\n\nReceived: " + this.utils.RECEIVED_COLOR(inspect(received)); + return { + pass, + message: pass + ? () => hint() + "Expected validation to fail, but it did not." + printReceived() + : () => + hint() + + "Expected validation to succeed, but it did not." + + printReceived() + + "\n\n" + + `Errors: ${this.utils.EXPECTED_COLOR(inspect(errors))}`, + }; + } finally { + errorContainer.errors = previousErrors; + } + }, +}); +declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace jest { + interface Matchers { + toValidateUsing(validatorMethod: Validator, errorContainer: ErrorContainer): R; + toValidateUsing(validatorFunction: Validator & ErrorContainer): R; + } + } +} + +// Expose some of the SchemaGenerator internals for test use +export class TestSchemaGenerator extends SchemaGenerator { + public readonly program: ts.Program; + constructor(config: Config) { + const program: ts.Program = createProgram(config); + super(program, createParser(program, config), createFormatter(config), config); + } + + public getParsedType(fullName: string): BaseType { + const node = this.findNamedNode(fullName); + const type = this.nodeParser.createType(node, new Context()); + if (!type) { + throw new Error(`Could not find or parse type ${fullName}`); + } + return type; + } } export function assertValidSchema( @@ -26,6 +98,24 @@ export function assertValidSchema( jsDoc: Config["jsDoc"] = "none", extraTags?: Config["extraTags"], schemaId?: Config["schemaId"] +): () => void { + return assertValidSchemaEx(relativePath, type, { jsDoc, extraTags, schemaId }); +} + +export function assertValidSchemaEx( + relativePath: string, + type?: string, + { + valid, + invalid, + jsDoc = "none", + extraTags, + schemaId, + ...configOptions + }: Config & { + valid?: string; + invalid?: string; + } = {} ) { return (): void => { const config: Config = { @@ -34,13 +124,14 @@ export function assertValidSchema( jsDoc, extraTags, skipTypeCheck: !!process.env.FAST_TEST, + ...configOptions, }; if (schemaId) { config.schemaId = schemaId; } - const generator = createGenerator(config); + const generator = new TestSchemaGenerator(config); const schema = generator.createSchema(type); const schemaFile = resolve(`${basePath}/${relativePath}/schema.json`); @@ -60,8 +151,23 @@ export function assertValidSchema( addFormats(localValidator); } - localValidator.validateSchema(actual); - expect(localValidator.errors).toBeNull(); - localValidator.compile(actual); // Will find MissingRef errors + expect(actual).toValidateUsing(localValidator.validateSchema, localValidator); + const validate = localValidator.compile(actual); // Will find MissingRef errors + + if (valid) { + const validType = generator.getParsedType(valid); + const exemplars = makeExemplars(validType); + for (const ex of exemplars) { + expect(ex).toValidateUsing(validate); + } + } + + if (invalid) { + const invalidType = generator.getParsedType(invalid); + const exemplars = makeExemplars(invalidType); + for (const ex of exemplars) { + expect(ex).not.toValidateUsing(validate); + } + } }; } diff --git a/test/valid-data-type.test.ts b/test/valid-data-type.test.ts index 2c055dfdb..6e9151cc4 100644 --- a/test/valid-data-type.test.ts +++ b/test/valid-data-type.test.ts @@ -1,6 +1,9 @@ -import { assertValidSchema } from "./utils"; +import { assertValidSchema, assertValidSchemaEx } from "./utils"; describe("valid-data-type", () => { + // default export names for the valid/invalid exemplar types + const valid = "VALID", + invalid = "INVALID"; it("type-aliases-primitive", assertValidSchema("type-aliases-primitive", "MyString")); it( "type-aliases-primitive-with-id", @@ -35,6 +38,10 @@ describe("valid-data-type", () => { it("type-regexp", assertValidSchema("type-regexp", "MyObject")); it("type-union", assertValidSchema("type-union", "TypeUnion")); it("type-union-tagged", assertValidSchema("type-union-tagged", "Shape")); + it( + "type-union-weird", + assertValidSchemaEx("type-union-weird", "WeirdUnion", { valid, invalid, jsDoc: "extended" }) + ); it("type-intersection", assertValidSchema("type-intersection", "MyObject")); it("type-intersection-conflict", assertValidSchema("type-intersection-conflict", "MyObject")); it("type-intersection-partial-conflict", assertValidSchema("type-intersection-partial-conflict", "MyType")); diff --git a/test/valid-data/type-union-weird/main.ts b/test/valid-data/type-union-weird/main.ts new file mode 100644 index 000000000..a7bfac8d2 --- /dev/null +++ b/test/valid-data/type-union-weird/main.ts @@ -0,0 +1,72 @@ +interface UnionElements { + /** + * Has a definition + */ + stringWithDefinition: string; + /** + * Also has a definition + */ + numberWithDefinition: number; + stringWithoutDefinition: string; + numberWithoutDefinition: number; + /** + * @minLength 10 + */ + stringWithMinLength: string; + /** + * @pattern ^\d+$ + */ + stringWithOnlyDigits: string; + /** + * @minimum 10 + */ + numberWithMinimum: number; + /** + * @maximum 5 + */ + numberWithMaximum: number; + enumString: 'a' | 'b' | 'c'; + enumNumber: 1 | 2 | 3; +} + +export interface WeirdUnion { + noCollapse1?: UnionElements['stringWithDefinition'] | UnionElements['numberWithDefinition']; + noCollapse2?: UnionElements['stringWithoutDefinition'] | UnionElements['enumNumber']; + noCollapse3?: UnionElements['numberWithoutDefinition'] | UnionElements['enumNumber']; + collapse1?: UnionElements['stringWithDefinition'] | UnionElements['numberWithoutDefinition']; + collapse2?: UnionElements['stringWithoutDefinition'] | UnionElements['numberWithoutDefinition']; + actuallyAnyString?: UnionElements['stringWithMinLength'] | UnionElements['stringWithoutDefinition']; + actuallyAnyNumber?: UnionElements['numberWithMaximum'] | UnionElements['numberWithoutDefinition']; + digitsOrLongString?: UnionElements['stringWithMinLength'] | UnionElements['stringWithOnlyDigits']; + digitsOrNumber?: UnionElements['stringWithOnlyDigits'] | UnionElements['numberWithDefinition']; + numberWithMinOrMax?: UnionElements['numberWithMinimum'] | UnionElements['numberWithMaximum']; +} + +const validList = [ + { + actuallyAnyString: 'short', + actuallyAnyNumber: 1234, + digitsOrLongString: '1234', + digitsOrNumber: '1234', + numberWithMinOrMax: 0, + }, + { + digitsOrLongString: 'veryverylong', + digitsOrNumber: 1234, + numberWithMinOrMax: 1234, + }, +] as const; +export const invalidList = [ + { + digitsOrLongString: 'short', + }, + { + digitsOrNumber: 'word', + }, + { + numberWithMinOrMax: 7, + } +] as const; + +export type VALID = typeof validList[number]; +export type INVALID = typeof invalidList[number]; diff --git a/test/valid-data/type-union-weird/schema.json b/test/valid-data/type-union-weird/schema.json new file mode 100644 index 000000000..9f6229b91 --- /dev/null +++ b/test/valid-data/type-union-weird/schema.json @@ -0,0 +1,112 @@ +{ + "$ref": "#/definitions/WeirdUnion", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "WeirdUnion": { + "additionalProperties": false, + "properties": { + "actuallyAnyNumber": { + "anyOf": [ + { + "maximum": 5, + "type": "number" + }, + { + "type": "number" + } + ] + }, + "actuallyAnyString": { + "type": "string" + }, + "collapse1": { + "description": "Has a definition", + "type": [ + "string", + "number" + ] + }, + "collapse2": { + "type": [ + "string", + "number" + ] + }, + "digitsOrLongString": { + "type": "string" + }, + "digitsOrNumber": { + "description": "Also has a definition", + "pattern": "^\\d+$", + "type": [ + "string", + "number" + ] + }, + "noCollapse1": { + "anyOf": [ + { + "description": "Has a definition", + "type": "string" + }, + { + "description": "Also has a definition", + "type": "number" + } + ] + }, + "noCollapse2": { + "anyOf": [ + { + "type": "string" + }, + { + "const": 1, + "type": "number" + }, + { + "const": 2, + "type": "number" + }, + { + "const": 3, + "type": "number" + } + ] + }, + "noCollapse3": { + "anyOf": [ + { + "type": "number" + }, + { + "const": 1, + "type": "number" + }, + { + "const": 2, + "type": "number" + }, + { + "const": 3, + "type": "number" + } + ] + }, + "numberWithMinOrMax": { + "anyOf": [ + { + "minimum": 10, + "type": "number" + }, + { + "maximum": 5, + "type": "number" + } + ] + } + }, + "type": "object" + } + } +} diff --git a/test/vega-lite.test.ts b/test/vega-lite.test.ts index 529707330..0a174dc61 100644 --- a/test/vega-lite.test.ts +++ b/test/vega-lite.test.ts @@ -1,7 +1,7 @@ import { readFileSync, writeFileSync } from "fs"; import { resolve } from "path"; import { Config } from "../src/Config"; -import { createGenerator } from "./utils"; +import { TestSchemaGenerator } from "./utils"; import stringify from "json-stable-stringify"; import Ajv from "ajv"; import addFormats from "ajv-formats"; @@ -16,7 +16,7 @@ describe("vega-lite", () => { skipTypeCheck: true, }; - const generator = createGenerator(config); + const generator = new TestSchemaGenerator(config); const schema = generator.createSchema(type); const schemaFile = resolve("test/vega-lite/schema.json"); From 58f0dc9f8dafa887884c5af16f07b4403a6940a0 Mon Sep 17 00:00:00 2001 From: Danielle Church Date: Sat, 27 Mar 2021 21:40:08 -0400 Subject: [PATCH 6/7] Adding enum-merging to mergeDefinitions, unit test --- src/Utils/mergeDefinitions.ts | 52 ++++- test/unit/mergeDefinitions.test.ts | 62 ++++++ test/valid-data/type-union-weird/schema.json | 36 ++-- test/vega-lite/schema.json | 197 ++++++++++--------- 4 files changed, 223 insertions(+), 124 deletions(-) create mode 100644 test/unit/mergeDefinitions.test.ts diff --git a/src/Utils/mergeDefinitions.ts b/src/Utils/mergeDefinitions.ts index 749392a27..cee07fa8c 100644 --- a/src/Utils/mergeDefinitions.ts +++ b/src/Utils/mergeDefinitions.ts @@ -1,5 +1,4 @@ import { Definition } from "../Schema/Definition"; -import { uniqueArray } from "./uniqueArray"; /** * Attempt to merge two disjoint definitions into one. Definitions are disjoint @@ -8,18 +7,55 @@ import { uniqueArray } from "./uniqueArray"; * 2) The cross-type validation properties 'enum' and 'const' are not on either definition, and * 3) The two definitions have no properties besides 'type' in common. * + * OR, if the following are true: + * 1) Each has an 'enum' or 'const' property (which is treated as an enum with one element) + * 2) They both have a 'type' property (which will be merged and deduplicated) or both do not + * 3) They share no other properties besides, possibly, 'type' * Returns the merged definition, or null if the two defs were not disjoint. */ export function mergeDefinitions(def1: Definition, def2: Definition): Definition | null { - const { type: type1, ...props1 } = def1; - const { type: type2, ...props2 } = def2; - const types = [type1!, type2!].flat(); - if (!type1 || !type2 || uniqueArray(types).length !== types.length) { + if (def1.$ref || def2.$ref) { + // pointer definitions are never mergeable return null; } - const keys = [...Object.keys(props1), ...Object.keys(props2)]; - if (keys.includes("enum") || keys.includes("const") || uniqueArray(keys).length !== keys.length) { + const { type: type1, enum: enum1, props: props1 } = splitTypesAndEnums(def1); + const { type: type2, enum: enum2, props: props2 } = splitTypesAndEnums(def2); + const result: Definition = { + ...props1, + ...props2, + }; + + if (Object.keys(result).length !== Object.keys(props1).length + Object.keys(props2).length) { + // shared properties - unmergeable + return null; + } + + if (enum1 && enum2) { + if (type1 && type2) { + result.type = Array.from(new Set(type1.concat(type2))); + } else if (type1 || type2) { + // one is typed and one isn't - unmergeable + return null; + } + result.enum = Array.from(new Set(enum1.concat(enum2))); + } else if (type1 && type2 && !enum1 && !enum2) { + if (type1.some((t) => type2.includes(t))) { + // shared types - unmergeable + return null; + } + result.type = type1.concat(type2); + } else { return null; } - return { type: types, ...props1, ...props2 }; + + return result; +} + +function splitTypesAndEnums(def: Definition) { + const { type: _type, const: _const, enum: _enum, ...props } = def; + return { + type: typeof _type === "string" ? [_type] : _type, + enum: typeof _const !== "undefined" ? [_const] : _enum, + props, + }; } diff --git a/test/unit/mergeDefinitions.test.ts b/test/unit/mergeDefinitions.test.ts new file mode 100644 index 000000000..529fd2048 --- /dev/null +++ b/test/unit/mergeDefinitions.test.ts @@ -0,0 +1,62 @@ +import { Definition } from "../../src/Schema/Definition"; +import { mergeDefinitions } from "../../src/Utils/mergeDefinitions"; + +function assertMerges(def1: Definition, def2: Definition, expected?: Definition) { + return () => { + const actual = mergeDefinitions(def1, def2); + if (expected) { + expect(actual).toEqual(expected); + } else { + expect(actual).not.toBeNull(); + } + const allKeys = Array.from(new Set(Object.keys(def1).concat(Object.keys(def2)))); + if (allKeys.includes("const")) { + // 'const' keys turn into 'enum' keys + allKeys.splice(allKeys.indexOf("const"), 1); + if (!allKeys.includes("enum")) { + allKeys.push("enum"); + } + } + expect(Object.keys(actual).sort()).toEqual(allKeys.sort()); + }; +} + +function assertDoesNotMerge(def1: Definition, def2: Definition) { + return () => { + const actual = mergeDefinitions(def1, def2); + expect(actual).toBeNull(); + }; +} + +describe("mergeDefinitions", () => { + it( + "merges simple unlike types", + assertMerges({ type: "string" }, { type: "number" }, { type: ["string", "number"] }) + ); + it("does not merge identical types", assertDoesNotMerge({ type: "number" }, { type: "number" })); + it( + "merges complex unlike types", + assertMerges({ type: "object", additionalProperties: false }, { type: "array", additionalItems: false }) + ); + it( + "does not merge types that share properties", + assertDoesNotMerge({ type: "string", description: "a string" }, { type: "number", description: "a number" }) + ); + it("merges consts into an enum", assertMerges({ const: "one" }, { const: "two" }, { enum: ["one", "two"] })); + it("merges const into existing enum", assertMerges({ enum: [1, 2] }, { const: 3 }, { enum: [1, 2, 3] })); + it("merges two enums", assertMerges({ enum: [1, 2] }, { enum: [3, 4] }, { enum: [1, 2, 3, 4] })); + it("dedupes merged enums", assertMerges({ enum: [1, 2] }, { enum: [2, 3] }, { enum: [1, 2, 3] })); + it( + "merges types in merged enums", + assertMerges( + { const: "one", type: "string" }, + { const: 2, type: "number" }, + { enum: ["one", 2], type: ["string", "number"] } + ) + ); + it( + "does not merge enums that share properties", + assertDoesNotMerge({ const: 1, description: "#1" }, { const: "two", description: "#2" }) + ); + it("does not merge an enum with a non-enum", assertDoesNotMerge({ const: 1 }, { type: "string" })); +}); diff --git a/test/valid-data/type-union-weird/schema.json b/test/valid-data/type-union-weird/schema.json index 9f6229b91..50acccb21 100644 --- a/test/valid-data/type-union-weird/schema.json +++ b/test/valid-data/type-union-weird/schema.json @@ -61,16 +61,14 @@ "type": "string" }, { - "const": 1, - "type": "number" - }, - { - "const": 2, - "type": "number" - }, - { - "const": 3, - "type": "number" + "enum": [ + 1, + 2, + 3 + ], + "type": [ + "number" + ] } ] }, @@ -80,16 +78,14 @@ "type": "number" }, { - "const": 1, - "type": "number" - }, - { - "const": 2, - "type": "number" - }, - { - "const": 3, - "type": "number" + "enum": [ + 1, + 2, + 3 + ], + "type": [ + "number" + ] } ] }, diff --git a/test/vega-lite/schema.json b/test/vega-lite/schema.json index 60a968ace..615024fbf 100644 --- a/test/vega-lite/schema.json +++ b/test/vega-lite/schema.json @@ -12111,12 +12111,13 @@ "type": "boolean" }, { - "const": "parity", - "type": "string" - }, - { - "const": "greedy", - "type": "string" + "enum": [ + "parity", + "greedy" + ], + "type": [ + "string" + ] } ] }, @@ -18404,20 +18405,15 @@ ] }, { - "const": "string", - "type": "string" - }, - { - "const": "boolean", - "type": "string" - }, - { - "const": "date", - "type": "string" - }, - { - "const": "number", - "type": "string" + "enum": [ + "string", + "boolean", + "date", + "number" + ], + "type": [ + "string" + ] } ] }, @@ -22947,12 +22943,13 @@ "type": "number" }, { - "const": "width", - "type": "string" - }, - { - "const": "height", - "type": "string" + "enum": [ + "width", + "height" + ], + "type": [ + "string" + ] }, { "$ref": "#/definitions/ExprRef" @@ -23110,12 +23107,13 @@ "type": "number" }, { - "const": "width", - "type": "string" - }, - { - "const": "height", - "type": "string" + "enum": [ + "width", + "height" + ], + "type": [ + "string" + ] }, { "$ref": "#/definitions/ExprRef" @@ -23424,12 +23422,13 @@ "type": "number" }, { - "const": "width", - "type": "string" - }, - { - "const": "height", - "type": "string" + "enum": [ + "width", + "height" + ], + "type": [ + "string" + ] }, { "$ref": "#/definitions/ExprRef" @@ -23510,12 +23509,13 @@ "type": "number" }, { - "const": "width", - "type": "string" - }, - { - "const": "height", - "type": "string" + "enum": [ + "width", + "height" + ], + "type": [ + "string" + ] }, { "$ref": "#/definitions/ExprRef" @@ -24766,12 +24766,13 @@ "type": "number" }, { - "const": "width", - "type": "string" - }, - { - "const": "height", - "type": "string" + "enum": [ + "width", + "height" + ], + "type": [ + "string" + ] }, { "$ref": "#/definitions/ExprRef" @@ -24852,12 +24853,13 @@ "type": "number" }, { - "const": "width", - "type": "string" - }, - { - "const": "height", - "type": "string" + "enum": [ + "width", + "height" + ], + "type": [ + "string" + ] }, { "$ref": "#/definitions/ExprRef" @@ -25161,12 +25163,13 @@ "type": "number" }, { - "const": "width", - "type": "string" - }, - { - "const": "height", - "type": "string" + "enum": [ + "width", + "height" + ], + "type": [ + "string" + ] }, { "$ref": "#/definitions/ExprRef" @@ -25247,12 +25250,13 @@ "type": "number" }, { - "const": "width", - "type": "string" - }, - { - "const": "height", - "type": "string" + "enum": [ + "width", + "height" + ], + "type": [ + "string" + ] }, { "$ref": "#/definitions/ExprRef" @@ -25507,12 +25511,13 @@ "type": "number" }, { - "const": "width", - "type": "string" - }, - { - "const": "height", - "type": "string" + "enum": [ + "width", + "height" + ], + "type": [ + "string" + ] }, { "$ref": "#/definitions/ExprRef" @@ -25593,12 +25598,13 @@ "type": "number" }, { - "const": "width", - "type": "string" - }, - { - "const": "height", - "type": "string" + "enum": [ + "width", + "height" + ], + "type": [ + "string" + ] }, { "$ref": "#/definitions/ExprRef" @@ -26222,19 +26228,17 @@ "TextBaseline": { "anyOf": [ { - "const": "alphabetic", - "type": "string" + "enum": [ + "alphabetic", + "line-top", + "line-bottom" + ], + "type": [ + "string" + ] }, { "$ref": "#/definitions/Baseline" - }, - { - "const": "line-top", - "type": "string" - }, - { - "const": "line-bottom", - "type": "string" } ] }, @@ -29345,12 +29349,13 @@ "type": "number" }, { - "const": "width", - "type": "string" - }, - { - "const": "height", - "type": "string" + "enum": [ + "width", + "height" + ], + "type": [ + "string" + ] }, { "$ref": "#/definitions/ExprRef" From 79d185559857554e039e99a65a97ebbc1f56e852 Mon Sep 17 00:00:00 2001 From: Danielle Church Date: Sun, 28 Mar 2021 00:35:29 -0400 Subject: [PATCH 7/7] mergeDefinitions works on trivially-mergable types In a few simple cases like (1 | number), mergeDefinitions will now correctly collapse two not-quite-disjoint type definitions together, by discarding the validation restrictions on the more-restricted type. This replaces the string-merging logic in UnionTypeFormatter, which was discarding annotations. --- src/TypeFormatter/UnionTypeFormatter.ts | 19 --- src/Utils/mergeDefinitions.ts | 152 +++++++++++++++++-- test/unit/mergeDefinitions.test.ts | 51 +++++-- test/valid-data/type-union-weird/main.ts | 2 +- test/valid-data/type-union-weird/schema.json | 43 ++---- test/vega-lite/schema.json | 73 ++------- 6 files changed, 212 insertions(+), 128 deletions(-) diff --git a/src/TypeFormatter/UnionTypeFormatter.ts b/src/TypeFormatter/UnionTypeFormatter.ts index 49786aba0..3682e0fbd 100644 --- a/src/TypeFormatter/UnionTypeFormatter.ts +++ b/src/TypeFormatter/UnionTypeFormatter.ts @@ -16,25 +16,6 @@ export class UnionTypeFormatter implements SubTypeFormatter { public getDefinition(type: UnionType): Definition { const definitions = type.getTypes().map((item) => this.childTypeFormatter.getDefinition(item)); - // TODO: why is this not covered by LiteralUnionTypeFormatter? - // special case for string literals | string -> string - let stringType = true; - let oneNotEnum = false; - for (const def of definitions) { - if (def.type !== "string") { - stringType = false; - break; - } - if (def.enum === undefined) { - oneNotEnum = true; - } - } - if (stringType && oneNotEnum) { - return { - type: "string", - }; - } - const flattenedDefinitions: JSONSchema7[] = []; // Flatten anyOf inside anyOf unless the anyOf has an annotation diff --git a/src/Utils/mergeDefinitions.ts b/src/Utils/mergeDefinitions.ts index cee07fa8c..5bb06ccaf 100644 --- a/src/Utils/mergeDefinitions.ts +++ b/src/Utils/mergeDefinitions.ts @@ -1,4 +1,9 @@ import { Definition } from "../Schema/Definition"; +import { RawTypeName } from "../Schema/RawType"; + +const isIntegerKey = "\0isInteger"; +type _Definition = Definition & { [isIntegerKey]?: true }; +type DefinitionProp = keyof _Definition; /** * Attempt to merge two disjoint definitions into one. Definitions are disjoint @@ -20,14 +25,33 @@ export function mergeDefinitions(def1: Definition, def2: Definition): Definition } const { type: type1, enum: enum1, props: props1 } = splitTypesAndEnums(def1); const { type: type2, enum: enum2, props: props2 } = splitTypesAndEnums(def2); - const result: Definition = { - ...props1, - ...props2, - }; + const result: _Definition = {}; - if (Object.keys(result).length !== Object.keys(props1).length + Object.keys(props2).length) { - // shared properties - unmergeable - return null; + const def1Validations: Partial> = {}; + const def2Validations: Partial> = {}; + for (const [props, validations] of [ + [props1, def1Validations], + [props2, def2Validations], + ] as const) { + for (const prop of Object.keys(props) as DefinitionProp[]) { + const value = props[prop]; + const propValidationType = propertyValidationMap[prop]; + if (!propValidationType) { + // assume this is a general validation property, bail + return null; + } + if (prop in result && value !== result[prop]) { + // shared annotation without identical values, unmergeable + return null; + } + if (typeof propValidationType === "string") { + validations[propValidationType] ??= {}; + // Typescript gets sad about how many properties there are. + (validations[propValidationType] as any)[prop] = value; + } else { + (result as any)[prop] = value; + } + } } if (enum1 && enum2) { @@ -38,24 +62,126 @@ export function mergeDefinitions(def1: Definition, def2: Definition): Definition return null; } result.enum = Array.from(new Set(enum1.concat(enum2))); + } else if ( + enum1 && + !enum2 && + type1?.every((t) => type2?.includes(t) && !def1Validations[t] && !def2Validations[t]) + ) { + // enum vs non-enum - can be collapsed if the non-enum shares type and there are no validations + result.type = type2; + } else if ( + enum2 && + !enum1 && + type2?.every((t) => type1?.includes(t) && !def1Validations[t] && !def2Validations[t]) + ) { + result.type = type1; } else if (type1 && type2 && !enum1 && !enum2) { - if (type1.some((t) => type2.includes(t))) { - // shared types - unmergeable - return null; + const allTypes = Array.from(new Set(type1.concat(type2))); + // Check every type represented in either def. Possibilities are: + // 1) Included in only one def. Include validations from that def. + // 2) Included in both defs, with validations in only one. Include without validations. + // 3) Included in both defs, validations in one are a strict subset of the other. Include the less-constrained. + // 4) Incompatible validations. Bail. + for (const type of allTypes) { + const typeValidations1 = def1Validations[type]; + const typeValidations2 = def2Validations[type]; + let useValidations: _Definition | undefined; + if (!type1.includes(type)) { + useValidations = typeValidations2; + } else if (!type2.includes(type)) { + useValidations = typeValidations1; + } else if (!typeValidations1 || !typeValidations2) { + // No validations, since we know both defs have the type + } else if ( + Object.entries(typeValidations1).every(([k, v]) => typeValidations2[k as DefinitionProp] === v) + ) { + // typeValidations1 is a strict subset of typeValidations2 + useValidations = typeValidations1; + } else if ( + Object.entries(typeValidations2).every(([k, v]) => typeValidations1[k as DefinitionProp] === v) + ) { + // typeValidations2 is a strict subset of typeValidations1 + useValidations = typeValidations2; + } else { + // incompatible validations for this type + return null; + } + if (useValidations) { + Object.assign(result, useValidations); + } } - result.type = type1.concat(type2); + result.type = allTypes; } else { return null; } + if (result[isIntegerKey]) { + if (Array.isArray(result.type) && result.type.includes("number")) { + result.type[result.type.indexOf("number")] = "integer"; + } + delete result[isIntegerKey]; + } + + if (Array.isArray(result.type) && result.type.length === 1) { + result.type = result.type[0]; + } + return result; } function splitTypesAndEnums(def: Definition) { const { type: _type, const: _const, enum: _enum, ...props } = def; - return { + const result = { type: typeof _type === "string" ? [_type] : _type, enum: typeof _const !== "undefined" ? [_const] : _enum, - props, + props: props as _Definition, }; + if (result.type?.includes("integer")) { + // type integer is effectively a constraint on type number. Treat it as such for now. + if (!result.type.includes("number")) { + result.type.push("number"); + } + result.props[isIntegerKey] = true; + } + return result; } + +const typeValidations: Record = { + array: ["items", "additionalItems", "maxItems", "minItems", "uniqueItems", "contains"], + boolean: [], + integer: [], + null: [], + number: ["multipleOf", "maximum", "exclusiveMaximum", "minimum", "exclusiveMinimum", isIntegerKey], + object: [ + "maxProperties", + "minProperties", + "required", + "properties", + "patternProperties", + "additionalProperties", + "dependencies", + "propertyNames", + ], + string: ["maxLength", "minLength", "pattern"], +}; + +const knownAnnotations: DefinitionProp[] = [ + "title", + "description", + "default", + "readOnly", + "writeOnly", + "examples", + "$comment", + "contentEncoding", + "contentMediaType", +]; + +// Anything that isn't in one of the above lists is assumed to be a general validation keyword +const propertyValidationMap: Partial> = Object.fromEntries( + knownAnnotations + .map((prop) => [prop, true as RawTypeName | true]) + .concat( + ...Object.entries(typeValidations).map(([type, props]) => props.map((prop) => [prop, type as RawTypeName])) + ) +); diff --git a/test/unit/mergeDefinitions.test.ts b/test/unit/mergeDefinitions.test.ts index 529fd2048..1037a2849 100644 --- a/test/unit/mergeDefinitions.test.ts +++ b/test/unit/mergeDefinitions.test.ts @@ -8,16 +8,16 @@ function assertMerges(def1: Definition, def2: Definition, expected?: Definition) expect(actual).toEqual(expected); } else { expect(actual).not.toBeNull(); - } - const allKeys = Array.from(new Set(Object.keys(def1).concat(Object.keys(def2)))); - if (allKeys.includes("const")) { - // 'const' keys turn into 'enum' keys - allKeys.splice(allKeys.indexOf("const"), 1); - if (!allKeys.includes("enum")) { - allKeys.push("enum"); + const allKeys = Array.from(new Set(Object.keys(def1).concat(Object.keys(def2)))); + if (allKeys.includes("const")) { + // 'const' keys turn into 'enum' keys + allKeys.splice(allKeys.indexOf("const"), 1); + if (!allKeys.includes("enum")) { + allKeys.push("enum"); + } } + expect(Object.keys(actual).sort()).toEqual(allKeys.sort()); } - expect(Object.keys(actual).sort()).toEqual(allKeys.sort()); }; } @@ -33,7 +33,6 @@ describe("mergeDefinitions", () => { "merges simple unlike types", assertMerges({ type: "string" }, { type: "number" }, { type: ["string", "number"] }) ); - it("does not merge identical types", assertDoesNotMerge({ type: "number" }, { type: "number" })); it( "merges complex unlike types", assertMerges({ type: "object", additionalProperties: false }, { type: "array", additionalItems: false }) @@ -59,4 +58,38 @@ describe("mergeDefinitions", () => { assertDoesNotMerge({ const: 1, description: "#1" }, { const: "two", description: "#2" }) ); it("does not merge an enum with a non-enum", assertDoesNotMerge({ const: 1 }, { type: "string" })); + + it( + "merges types with identical annotations", + assertMerges( + { type: "string", title: "title" }, + { type: "number", title: "title" }, + { type: ["string", "number"], title: "title" } + ) + ); + it( + "merges same types with no validations", + assertMerges({ type: "string" }, { type: "string" }, { type: "string" }) + ); + it( + "merges same types with identical validations", + assertMerges( + { type: "string", minLength: 5 }, + { type: "string", minLength: 5 }, + { type: "string", minLength: 5 } + ) + ); + it( + "does not merge same types with different validations", + assertDoesNotMerge({ type: "string", minLength: 5 }, { type: "string", maxLength: 10 }) + ); + it( + "collapses types with validations into types without", + assertMerges({ type: "string" }, { type: "string", minLength: 5 }, { type: "string" }) + ); + it( + "collapses types with enums into types without validations", + assertMerges({ type: "number" }, { type: "number", const: 7 }, { type: "number" }) + ); + it("does not merge when general validations are present", assertDoesNotMerge({ type: "string" }, { anyOf: [] })); }); diff --git a/test/valid-data/type-union-weird/main.ts b/test/valid-data/type-union-weird/main.ts index a7bfac8d2..1841ddd61 100644 --- a/test/valid-data/type-union-weird/main.ts +++ b/test/valid-data/type-union-weird/main.ts @@ -32,9 +32,9 @@ interface UnionElements { export interface WeirdUnion { noCollapse1?: UnionElements['stringWithDefinition'] | UnionElements['numberWithDefinition']; noCollapse2?: UnionElements['stringWithoutDefinition'] | UnionElements['enumNumber']; - noCollapse3?: UnionElements['numberWithoutDefinition'] | UnionElements['enumNumber']; collapse1?: UnionElements['stringWithDefinition'] | UnionElements['numberWithoutDefinition']; collapse2?: UnionElements['stringWithoutDefinition'] | UnionElements['numberWithoutDefinition']; + collapse3?: UnionElements['numberWithoutDefinition'] | UnionElements['enumNumber']; actuallyAnyString?: UnionElements['stringWithMinLength'] | UnionElements['stringWithoutDefinition']; actuallyAnyNumber?: UnionElements['numberWithMaximum'] | UnionElements['numberWithoutDefinition']; digitsOrLongString?: UnionElements['stringWithMinLength'] | UnionElements['stringWithOnlyDigits']; diff --git a/test/valid-data/type-union-weird/schema.json b/test/valid-data/type-union-weird/schema.json index 50acccb21..03d4fb902 100644 --- a/test/valid-data/type-union-weird/schema.json +++ b/test/valid-data/type-union-weird/schema.json @@ -6,15 +6,7 @@ "additionalProperties": false, "properties": { "actuallyAnyNumber": { - "anyOf": [ - { - "maximum": 5, - "type": "number" - }, - { - "type": "number" - } - ] + "type": "number" }, "actuallyAnyString": { "type": "string" @@ -32,8 +24,20 @@ "number" ] }, + "collapse3": { + "type": "number" + }, "digitsOrLongString": { - "type": "string" + "anyOf": [ + { + "minLength": 10, + "type": "string" + }, + { + "pattern": "^\\d+$", + "type": "string" + } + ] }, "digitsOrNumber": { "description": "Also has a definition", @@ -66,26 +70,7 @@ 2, 3 ], - "type": [ - "number" - ] - } - ] - }, - "noCollapse3": { - "anyOf": [ - { "type": "number" - }, - { - "enum": [ - 1, - 2, - 3 - ], - "type": [ - "number" - ] } ] }, diff --git a/test/vega-lite/schema.json b/test/vega-lite/schema.json index 615024fbf..fc6b55dec 100644 --- a/test/vega-lite/schema.json +++ b/test/vega-lite/schema.json @@ -12115,9 +12115,7 @@ "parity", "greedy" ], - "type": [ - "string" - ] + "type": "string" } ] }, @@ -18397,24 +18395,9 @@ "type": "object" }, "ParseValue": { - "anyOf": [ - { - "type": [ - "null", - "string" - ] - }, - { - "enum": [ - "string", - "boolean", - "date", - "number" - ], - "type": [ - "string" - ] - } + "type": [ + "null", + "string" ] }, "PivotTransform": { @@ -22947,9 +22930,7 @@ "width", "height" ], - "type": [ - "string" - ] + "type": "string" }, { "$ref": "#/definitions/ExprRef" @@ -23111,9 +23092,7 @@ "width", "height" ], - "type": [ - "string" - ] + "type": "string" }, { "$ref": "#/definitions/ExprRef" @@ -23426,9 +23405,7 @@ "width", "height" ], - "type": [ - "string" - ] + "type": "string" }, { "$ref": "#/definitions/ExprRef" @@ -23513,9 +23490,7 @@ "width", "height" ], - "type": [ - "string" - ] + "type": "string" }, { "$ref": "#/definitions/ExprRef" @@ -24770,9 +24745,7 @@ "width", "height" ], - "type": [ - "string" - ] + "type": "string" }, { "$ref": "#/definitions/ExprRef" @@ -24857,9 +24830,7 @@ "width", "height" ], - "type": [ - "string" - ] + "type": "string" }, { "$ref": "#/definitions/ExprRef" @@ -25167,9 +25138,7 @@ "width", "height" ], - "type": [ - "string" - ] + "type": "string" }, { "$ref": "#/definitions/ExprRef" @@ -25254,9 +25223,7 @@ "width", "height" ], - "type": [ - "string" - ] + "type": "string" }, { "$ref": "#/definitions/ExprRef" @@ -25515,9 +25482,7 @@ "width", "height" ], - "type": [ - "string" - ] + "type": "string" }, { "$ref": "#/definitions/ExprRef" @@ -25602,9 +25567,7 @@ "width", "height" ], - "type": [ - "string" - ] + "type": "string" }, { "$ref": "#/definitions/ExprRef" @@ -26233,9 +26196,7 @@ "line-top", "line-bottom" ], - "type": [ - "string" - ] + "type": "string" }, { "$ref": "#/definitions/Baseline" @@ -29353,9 +29314,7 @@ "width", "height" ], - "type": [ - "string" - ] + "type": "string" }, { "$ref": "#/definitions/ExprRef"