cmudig/apigen

Generate from Vega-lite Spec

lan-lyu opened this issue · 4 comments

I used type checker and AST node to parse from vega-lite/src/spec/index.ts, and begin from this:

export type TopLevelSpec =
  | TopLevelUnitSpec<Field>
  | TopLevelFacetSpec
  | TopLevel<LayerSpec<Field>>
  | TopLevel<RepeatSpec>
  | TopLevel<GenericConcatSpec<NonNormalizedSpec>>
  | TopLevel<GenericVConcatSpec<NonNormalizedSpec>>
  | TopLevel<GenericHConcatSpec<NonNormalizedSpec>>;

Now it can parse the type inheritance like this (full text)

############### TopLevelUnitSpec<Field> ###############
####### TopLevelUnitSpec<Field> #######
- TopLevelUnitSpec<Field>
-- GenericUnitSpec<FacetedCompositeEncoding<Field>, AnyMark, TopLevelParameter>
-- ResolveMixins
-- GenericCompositionLayout
-- FrameMixins<any>
-- TopLevelProperties<any>
-- { $schema?: string; config?: Config<any>; datasets?: Datasets; usermeta?: Dict<unknown>; }
-- DataMixins
####### Field #######
- Field
-- string
-- RepeatRef

and get properties in these types (full text)

############### TopLevelUnitSpec<Field> ###############
####### TopLevelUnitSpec<Field> #######
### mark ###
### encoding ###
### projection ###
### params ###
### title ###
### name ###
### description ###
### data ###
### transform ###
### resolve ###
### align ###
### center ###
### spacing ###
### bounds ###
### view ###
### width ###
### height ###
### background ###
### padding ###
### autosize ###
### $schema ###
### config ###
### datasets ###
### usermeta ###
####### Field #######
### toString ###
### valueOf ###
### repeat ###

And types in each property such as mark (full text)

############### TopLevelUnitSpec<Field> ###############
####### TopLevelUnitSpec<Field> #######
### mark ###
AnyMark
"boxplot"
"errorbar"
"errorband"
BoxPlotDef
ErrorBarDef
ErrorBandDef
"arc"
"area"
"bar"
"image"
"line"
"point"
"rect"
"rule"
"text"
"tick"
"trail"
"circle"
"square"
"geoshape"
MarkDef<"arc" | "area" | "bar" | "image" | "line" | "point" | "rect" | "rule" | "text" | "tick" | "trail" | "circle" | "square" | "geoshape", any>

The code is in

if(statement.name.getText() === "TopLevelSpec") {

Recursively get the types and properties (code)

And generate a rough mark example directly from the types (text)

Vega-lite-api is using FacetedEncoding for generating encoding channels. While FacetedEncoding is in json-schema, it's not in vega-lite src types.
Is it because this json-schema is not updated with the types? If not, how does json-schema-generator come up with FacetedEncoding? @domoritz

There is a rename step because the names from the generator were not what we wanted: https://github.com/vega/vega-lite/blob/main/scripts/rename-schema.sh

This week's update:

Auto-generate files from typescript types

The generation logic is from vega-lite-api constants. When I meet a 'TopLevelUnitSpec', I generate mark and data.

  mark:     'TopLevelUnitSpec',
  layer:    'TopLevelLayerSpec',
  concat:   'TopLevelConcatSpec',
  hconcat:  'TopLevelHConcatSpec',
  vconcat:  'TopLevelVConcatSpec',
  _repeat:  'TopLevelRepeatSpec',
  _facet:   'TopLevelFacetSpec',
  spec:     'TopLevelSpec',
  data:     'TopLevelUnitSpec',

In this way, as long as we have a well-defined constant, all codes can be generated. But will that influence the maintainability of the code?

  • check if the generation logic is okay

Create Internal Representation (In Process)

Now when generating, the types still have inheritances, for example, boolean | RowCol<boolean> as shown below.

  center(value: boolean | RowCol<boolean>) {
    if (arguments.length) {
      const obj = copy(this);
      set(obj, "center", value);
      return obj;
    } else {
      return get(this, "center");
    }
  }

To solve this, I plan to have a class that stores the inheritance through children's classes so that we can get the oneLevelType from children's oneLevelType.

// internal representation of a type
class VegaLiteType {
    name: string;
    type: ts.Type;
    kind: TypeKind;
    children: VegaLiteType[];
    oneLevelType: string;
    properties?: ts.Type[];
    description?: string;   //TODO: get descirption and generate documentation

    constructor(name: string, type: ts.Type, kind: TypeKind, children: VegaLiteType[]) {
        this.name = name;
        this.type = type;
        this.kind = kind;
        this.children = children;
        // this.oneLevelType = this.updateOneLevelType();
        this.properties = (type as any).properties;
    }

    generateOneLevelType(): string {
        switch (this.kind) {
            case TypeKind.Union:
                return this.children.map(child => child.generateOneLevelType()).join(" | ");
            case TypeKind.Intersection:
                return this.children.map(child => child.generateOneLevelType()).join(" & ");
            case TypeKind.Literal:
                return this.name;
            case TypeKind.TypeParameter:
                return this.name;
            case TypeKind.Other:
                return this.name;
        }
    }

    updateOneLevelType(): void {
        this.oneLevelType = this.generateOneLevelType();
    }
}