Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add JSON schema generation. #358

Merged
merged 12 commits into from
Apr 20, 2024
74 changes: 69 additions & 5 deletions package-lock.json

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

12 changes: 9 additions & 3 deletions packages/spec/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,21 @@
},
"scripts": {
"prebuild": "rimraf dist && mkdir dist",
"build": "tsc && node ../../esbuild.js mosaic-spec",
"build": "npm run types && node ../../esbuild.js mosaic-spec",
"lint": "eslint src test",
"pretest": "tsc",
"types": "tsc -p tsconfig.json && npm run schema",
"schema": "ts-json-schema-generator -f tsconfig.json -p src/spec/Spec.ts -t Spec --no-type-check --no-ref-encode --functions hide > dist/mosaic-schema.json",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW this fails on a fresh clone of the repo bc the dist/ directory doesn't exist

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Yeah, this is still WIP, but also schema is not meant to typically be run in isolation; it's now tied in to both the prebuild and pretest hooks.

"pretest": "npm run prebuild && npm run types",
"test": "mocha 'test/**/*-test.js' && tsc -p jsconfig.json",
"prepublishOnly": "npm run test && npm run lint && npm run build"
},
"dependencies": {
"@uwdata/mosaic-core": "^0.7.1",
"@uwdata/mosaic-sql": "^0.7.0",
"@uwdata/vgplot": "^0.7.1"
"@uwdata/vgplot": "^0.7.1",
"ts-json-schema-generator": "^2.0.0"
},
"devDependencies": {
"ajv": "^8.12.0"
}
}
9 changes: 9 additions & 0 deletions packages/spec/src/spec/CSSStyles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
type OmittedProperties =
| 'parentRule'
| 'getPropertyPriority'
| 'getPropertyValue'
| 'item'
| 'removeProperty'
| 'setProperty';

export type CSSStyles = Partial<Omit<CSSStyleDeclaration, OmittedProperties>>;
2 changes: 1 addition & 1 deletion packages/spec/src/spec/Data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export type DataArray = object[];
/**
* A data definition that loads an external data file.
*/
export interface DataFile {
export interface DataFile extends DataBaseOptions {
/**
* The data file to load. If no type option is provided,
* the file suffix must be one of `.csv`, `.json`, or `.parquet`.
Expand Down
3 changes: 2 additions & 1 deletion packages/spec/src/spec/PlotAttribute.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { CSSStyles } from './CSSStyles.js';
import { ParamRef } from './Param.js';
import {
ColorScaleType, ColorScheme, ContinuousScaleType, DiscreteScaleType,
Expand Down Expand Up @@ -124,7 +125,7 @@ export interface PlotAttributes {
* [3]: https://www.w3.org/TR/css-values-4/#deprecated-quirky-length
* [4]: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#currentcolor_keyword
*/
style?: string | Partial<CSSStyleDeclaration> | null | ParamRef;
style?: string | CSSStyles | null | ParamRef;

/**
* How to distribute unused space in the **range** for *point* and *band*
Expand Down
17 changes: 4 additions & 13 deletions packages/spec/src/spec/marks/Marks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@ import { PlotMarkData } from '../PlotFrom.js';
import { CurveName, FrameAnchor, Interval, Reducer, ScaleName } from '../PlotTypes.js';
import { Transform } from '../Transform.js';

/**
* The set of known channel names. Channels in custom marks may use other names;
* these known names are enumerated for convenient autocomplete.
*/
/** The set of known channel names. */
export type ChannelName =
| 'ariaLabel'
| 'fill'
Expand Down Expand Up @@ -38,8 +35,7 @@ export type ChannelName =
| 'y'
| 'y1'
| 'y2'
| 'z'
| (string & Record<never, never>); // custom channel; see also https://github.com/microsoft/TypeScript/issues/29729
| 'z';

type ChannelScale = ScaleName | 'auto' | boolean | null;

Expand All @@ -49,13 +45,12 @@ type ChannelScale = ScaleName | 'auto' | boolean | null;
* - a field name, to extract the corresponding value for each datum
* - an iterable of values, typically of the same length as the data
* - a channel transform or SQL expression
* - a constant date, number, or boolean
* - a constant number or boolean
* - null to represent no value
*/
export type ChannelValue =
| any[] // column of values
| (string & Record<never, never>) // field or literal color; see also https://github.com/microsoft/TypeScript/issues/29729
| Date // constant
| number // constant
| boolean // constant
| null // constant
Expand Down Expand Up @@ -912,9 +907,7 @@ export interface CurveAutoOptions {
export type StackOffsetName =
| 'center'
| 'normalize'
| 'wiggle'
| ('expand' & Record<never, never>) // deprecated; use normalize
| ('silhouette' & Record<never, never>); // deprecated; use center
| 'wiggle';

/**
* A stack offset method; one of:
Expand Down Expand Up @@ -955,8 +948,6 @@ export type StackOrderName = 'value' | 'x' | 'y' | 'z' | 'sum' | 'appearance' |
*
* - a named stack order method such as *inside-out* or *sum*
* - a field name, for natural order of the corresponding values
* - an accessor function, for natural order of the corresponding values
* - a comparator function for ordering data
* - an array of explicit **z** values in the desired order
*/
export type StackOrder =
Expand Down
6 changes: 6 additions & 0 deletions packages/spec/test/load-specs.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,9 @@ export function loadJSON(name) {
export function loadESM(name) {
return readFile(join(ESM, `${name}.js`), 'utf8');
}

export async function loadJSONSchema() {
const path = join(__dirname, '../dist/mosaic-schema.json');
const text = await readFile(path, 'utf8');
return parse(text);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,25 @@
import assert from 'node:assert';
import { specs, loadJSON, loadESM } from './load-specs.js';
import ajv from 'ajv';
import { specs, loadJSON, loadJSONSchema, loadESM } from './load-specs.js';
import { astToESM, parseSpec } from '../src/index.js';

// initialize JSON schema validator
const validator = new ajv({
allErrors: true,
allowUnionTypes: true,
verbose: true
});
const schema = await loadJSONSchema();
const validate = validator.compile(schema);

// validate JSON schema
describe('JSON schema', () => {
it('is a valid JSON schema', () => {
assert.ok(validator.validateSchema(schema));
});
});

// validate specs, parsing, and generation
for (const [name, spec] of specs) {
describe(`Test specification: ${name}`, () => {
it(`produces esm output`, async () => {
Expand All @@ -23,5 +41,13 @@ for (const [name, spec] of specs) {
`${name} did not round trip unchanged`
);
});
it(`passes JSON schema validation`, () => {
const valid = validate(spec);
if (!valid) {
console.error(validate.errors);
}
assert.ok(valid);
assert.ok(parseSpec(spec).toJSON());
});
});
}