cmudig / apigen

API Generator from TypeScript
BSD 3-Clause "New" or "Revised" License
3 stars 0 forks source link

Generate from Vega-lite Spec #21

Open lan-lyu opened 1 year ago

lan-lyu commented 1 year ago

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 https://github.com/cmudig/apigen/blob/65ed0cc7d1555ffed0f6171e5b1b3b1b3f47e09e/explorationOnSpec/from-spec-index.ts#L77

lan-lyu commented 1 year ago

Recursively get the types and properties (code)

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

lan-lyu commented 1 year ago

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

domoritz commented 1 year ago

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

lan-lyu commented 1 year ago

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?

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();
    }
}