Skip to content

Commit

Permalink
refactor: pass data, parentData and parentDataProperty Names via Comp…
Browse files Browse the repository at this point in the history
…ilationContext
  • Loading branch information
epoberezkin committed Aug 27, 2020
1 parent 3eb4c02 commit 60c4b3c
Show file tree
Hide file tree
Showing 15 changed files with 139 additions and 566 deletions.
437 changes: 0 additions & 437 deletions lib/ajv.d._ts

This file was deleted.

13 changes: 11 additions & 2 deletions lib/compile/codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ export class Code {
}
}

export const nil = new Code("")

export class Name extends Code {}

const varKinds = {
Expand Down Expand Up @@ -97,7 +99,7 @@ export default class CodeGen {
return this._def(varKinds.var, nameOrPrefix, rhs)
}

assign(name: Name, rhs: Expression | number | boolean): CodeGen {
assign(name: Expression, rhs: Expression | number | boolean): CodeGen {
this.code(`${name} = ${rhs};`)
return this
}
Expand Down Expand Up @@ -164,6 +166,13 @@ export default class CodeGen {
return this
}

return(value: Block): CodeGen {
this._out += "return "
this.code(value)
this._out += ";"
return this
}

try(tryBody: Block, catchCode?: (e: Name) => void, finallyCode?: Block): CodeGen {
if (!catchCode && !finallyCode) throw new Error('CodeGen: "try" without "catch" and "finally"')
this.code("try{").code(tryBody)
Expand Down Expand Up @@ -196,7 +205,7 @@ export default class CodeGen {
return this
}

func(name = "", args = "", async?: boolean, funcBody?: Block): CodeGen {
func(name: Name, args: Code = nil, async?: boolean, funcBody?: Block): CodeGen {
this.#blocks.push(BlockKind.Func)
this.code(`${async ? "async " : ""}function ${name}(${args}){`)
if (funcBody) this.code(funcBody).endFunc()
Expand Down
11 changes: 8 additions & 3 deletions lib/compile/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import CodeGen, {Expression, Code} from "./codegen"
import CodeGen, {nil, Expression, Code, Name} from "./codegen"
import {toQuotedString} from "./util"
import {quotedString} from "../vocabularies/util"
import {validateFunctionCode} from "./validate"
Expand Down Expand Up @@ -109,9 +109,16 @@ function compile(schema, root, localRefs, baseId) {

const gen = new CodeGen()

const data = new Name("data")

validateFunctionCode({
gen,
allErrors: !!opts.allErrors,
data,
parentData: new Name("parentData"),
parentDataProperty: new Name("parentDataProperty"),
dataNames: [data],
dataPathArr: [nil],
topSchemaRef: new Code("validate.schema"),
async: _schema.$async === true,
schema: _schema,
Expand All @@ -122,9 +129,7 @@ function compile(schema, root, localRefs, baseId) {
schemaPath: "",
errSchemaPath: "#",
errorPath: '""',
dataPathArr: [""],
dataLevel: 0,
data: "data", // TODO get unique name when passed from applicator keywords
RULES, // TODO refactor - it is available on the instance
resolveRef, // TODO remove to imports
usePattern, // TODO remove to imports
Expand Down
9 changes: 9 additions & 0 deletions lib/compile/names.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {Name} from "./codegen"

const names = {
validate: new Name("validate"),
dataPath: new Name("dataPath"),
rootData: new Name("rootData"),
}

export default names
68 changes: 40 additions & 28 deletions lib/compile/subschema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,18 @@ import {quotedString, accessProperty} from "../vocabularies/util"
import {Code, Name, Expression} from "./codegen"

export interface SubschemaContext {
// TODO use Optional?
schema: object | boolean
schemaPath: string
errSchemaPath: string
topSchemaRef?: Code
errorPath?: string
dataPathArr?: (Expression | number)[]
dataLevel?: number
data?: Name
parentData?: Name
parentDataProperty?: Expression | number
dataNames?: Name[]
dataPathArr?: (Expression | number)[]
propertyName?: Name
compositeRule?: true
createErrors?: boolean
Expand All @@ -24,20 +29,22 @@ export enum Expr {
Str,
}

export interface SubschemaApplication {
keyword?: string
schemaProp?: string | number
schema?: object | boolean
schemaPath?: string
errSchemaPath?: string
topSchemaRef?: Code
data?: Name
dataProp?: Expression | number
propertyName?: Name
expr?: Expr
compositeRule?: true
createErrors?: boolean
allErrors?: boolean
export type SubschemaApplication = Partial<SubschemaApplicationParams>

interface SubschemaApplicationParams {
keyword: string
schemaProp: string | number
schema: object | boolean
schemaPath: string
errSchemaPath: string
topSchemaRef: Code
data: Name | Code
dataProp: Expression | number
propertyName: Name
expr: Expr
compositeRule: true
createErrors: boolean
allErrors: boolean
}

export function applySubschema(
Expand Down Expand Up @@ -99,31 +106,36 @@ function extendSubschemaData(
throw new Error('both "data" and "dataProp" passed, only one allowed')
}

const {gen} = it

if (dataProp !== undefined) {
const {gen, errorPath, dataPathArr, dataLevel, opts} = it
const {errorPath, dataPathArr, opts} = it
const nextData = gen.var("data", `${it.data}${accessProperty(dataProp)}`) // TODO var, tagged
dataContextProps(nextData)
// TODO possibly refactor getPath and getPathExpr to one function using Expr enum
const nextLevel = dataLevel + 1
subschema.errorPath =
dataProp instanceof Code
? getPathExpr(errorPath, dataProp, opts.jsonPointers, expr === Expr.Num)
: getPath(errorPath, dataProp, opts.jsonPointers)

subschema.dataPathArr = [
...dataPathArr,
expr === Expr.Const && typeof dataProp == "string" ? quotedString(dataProp) : dataProp,
]
subschema.dataLevel = nextLevel
subschema.parentDataProperty =
expr === Expr.Const && typeof dataProp == "string" ? quotedString(dataProp) : dataProp

const passDataProp = accessProperty(dataProp)
gen.code(`var data${nextLevel} = data${dataLevel || ""}${passDataProp};`)
subschema.dataPathArr = [...dataPathArr, subschema.parentDataProperty]
}

if (data !== undefined) {
const {gen, dataLevel} = it
const nextLevel = dataLevel + 1
subschema.dataLevel = nextLevel
const nextData = data instanceof Name ? data : gen.var("data", data) // TODO var, replaceable if used once?
dataContextProps(nextData)
if (propertyName !== undefined) subschema.propertyName = propertyName
gen.code(`var data${nextLevel} = ${data};`)
// TODO something is wrong here with not changing parentDataProperty and not appending dataPathArr
}

function dataContextProps(_nextData: Name) {
subschema.data = _nextData
subschema.dataLevel = it.dataLevel + 1
subschema.parentData = it.data
subschema.dataNames = [...it.dataNames, _nextData]
}
}

Expand Down
30 changes: 18 additions & 12 deletions lib/compile/util.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import {_, Code, Name, Expression} from "./codegen"
import {CompilationContext} from "../types"
import names from "./names"

export function checkDataType(
dataType: string,
Expand Down Expand Up @@ -137,33 +139,33 @@ export function getPath(

const JSON_POINTER = /^\/(?:[^~]|~0|~1)*$/
const RELATIVE_JSON_POINTER = /^([0-9]+)(#|\/(?:[^~]|~0|~1)*)?$/
export function getData($data: string, lvl: number, paths: (Expression | number)[]): string {
export function getData(
$data: string,
{dataLevel, dataNames, dataPathArr}: CompilationContext
): Expression | number {
let jsonPointer, data
if ($data === "") return "rootData"
if ($data === "") return names.rootData
if ($data[0] === "/") {
if (!JSON_POINTER.test($data)) {
throw new Error("Invalid JSON-pointer: " + $data)
}
jsonPointer = $data
data = "rootData"
data = names.rootData
} else {
const matches = RELATIVE_JSON_POINTER.exec($data)
if (!matches) throw new Error("Invalid JSON-pointer: " + $data)
const up: number = +matches[1]
jsonPointer = matches[2]
if (jsonPointer === "#") {
if (up >= lvl) {
throw new Error(
"Cannot access property/index " + up + " levels up, current level is " + lvl
)
if (up >= dataLevel) {
throw new Error(errorMsg("property/index", up))
}
return "" + paths[lvl - up]
return dataPathArr[dataLevel - up]
}

if (up > lvl) {
throw new Error("Cannot access data " + up + " levels up, current level is " + lvl)
}
data = "data" + (lvl - up || "")
if (up > dataLevel) throw new Error(errorMsg("data", up))

data = dataNames[dataLevel - up]
if (!jsonPointer) return data
}

Expand All @@ -176,6 +178,10 @@ export function getData($data: string, lvl: number, paths: (Expression | number)
}
}
return expr

function errorMsg(pointerType: string, up: number): string {
return `Cannot access ${pointerType} ${up} levels up, current level is ${dataLevel}`
}
}

export function joinPaths(a: string, b: string): string {
Expand Down
4 changes: 2 additions & 2 deletions lib/compile/validate/boolSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export function boolOrEmptySchema(it: CompilationContext, valid: Name): void {
}

function falseSchemaError(it: CompilationContext, overrideAllErrors?: boolean) {
const {gen, dataLevel} = it
const {gen, data} = it
// TODO maybe some other interface should be used for non-keyword validation errors...
const cxt: KeywordContext = {
gen,
Expand All @@ -37,7 +37,7 @@ function falseSchemaError(it: CompilationContext, overrideAllErrors?: boolean) {
fail: exception,
errorParams: exception,
keyword: "false schema",
data: new Name("data" + (dataLevel || "")), // TODO refactor dataLevel
data,
schema: false,
schemaCode: false,
schemaValue: false,
Expand Down
39 changes: 12 additions & 27 deletions lib/compile/validate/dataType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,13 @@ export function getSchemaTypes({schema, opts}: CompilationContext): string[] {
}

export function coerceAndCheckDataType(it: CompilationContext, types: string[]): boolean {
const {
gen,
dataLevel,
opts: {coerceTypes, strictNumbers},
} = it
const coerceTo = coerceToTypes(types, coerceTypes)
const {gen, data, opts} = it
const coerceTo = coerceToTypes(types, opts.coerceTypes)
const checkTypes =
types.length > 0 &&
!(coerceTo.length === 0 && types.length === 1 && schemaHasRulesForType(it, types[0]))
if (checkTypes) {
// TODO refactor `data${dataLevel || ""}`
const wrongType = checkDataTypes(types, new Name(`data${dataLevel || ""}`), strictNumbers, true)
const wrongType = checkDataTypes(types, data, opts.strictNumbers, true)
gen.if(wrongType, () => {
if (coerceTo.length) coerceData(it, coerceTo)
else reportTypeError(it)
Expand All @@ -54,26 +49,19 @@ function coerceToTypes(types: string[], coerceTypes?: boolean | "array"): string
}

export function coerceData(it: CompilationContext, coerceTo: string[]): void {
const {
gen,
schema,
dataLevel,
opts: {coerceTypes, strictNumbers},
} = it
// TODO move "data" to CompilationContext
const data = new Name(`data${dataLevel || ""}`)
const {gen, schema, data, opts} = it
const dataType = gen.let("dataType", `typeof ${data}`)
const coerced = gen.let("coerced")
if (coerceTypes === "array") {
if (opts.coerceTypes === "array") {
gen.if(_`${dataType} == 'object' && Array.isArray(${data}) && ${data}.length == 1`, () =>
gen
.code(_`${data} = ${data}[0]; ${dataType} = typeof ${data};`)
.if(checkDataType(schema.type, data, strictNumbers), _`${coerced} = ${data}`)
.if(checkDataType(schema.type, data, opts.strictNumbers), _`${coerced} = ${data}`)
)
}
gen.if(`${coerced} !== undefined`)
for (const t of coerceTo) {
if (t in COERCIBLE || (t === "array" && coerceTypes === "array")) {
if (t in COERCIBLE || (t === "array" && opts.coerceTypes === "array")) {
coerceSpecificType(t)
}
}
Expand Down Expand Up @@ -135,16 +123,13 @@ export function coerceData(it: CompilationContext, coerceTo: string[]): void {
}

function assignParentData(
{gen, dataLevel, dataPathArr}: CompilationContext,
{gen, parentData, parentDataProperty}: CompilationContext,
expr: string | Name
): void {
// TODO replace dataLevel
if (dataLevel) {
const parentData = "data" + (dataLevel - 1 || "")
gen.code(`${parentData}[${dataPathArr[dataLevel]}] = ${expr};`)
} else {
gen.if("parentData !== undefined", `parentData[parentDataProperty] = ${expr};`)
}
// TODO use gen.property
gen.if(`${parentData} !== undefined`, () =>
gen.assign(`${parentData}[${parentDataProperty}]`, expr)
)
}

const typeError: KeywordErrorDefinition = {
Expand Down
26 changes: 9 additions & 17 deletions lib/compile/validate/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,33 +13,25 @@ export function assignDefaults(it: CompilationContext, ty?: string): void {
}

function assignDefault(
{
gen,
compositeRule,
dataLevel,
useDefault,
opts: {strictDefaults, useDefaults},
logger,
}: CompilationContext,
{gen, compositeRule, data, useDefault, opts, logger}: CompilationContext,
prop: string | number,
defaultValue: any
): void {
if (defaultValue === undefined) return
// TODO refactor `data${dataLevel || ""}`
const data = "data" + (dataLevel || "") + getProperty(prop)
const childData = `${data}${getProperty(prop)}` // TODO tagged
if (compositeRule) {
if (strictDefaults) {
const msg = `default is ignored for: ${data}`
if (strictDefaults === "log") logger.warn(msg)
if (opts.strictDefaults) {
const msg = `default is ignored for: ${childData}`
if (opts.strictDefaults === "log") logger.warn(msg)
else throw new Error(msg)
}
return
}

const condition =
`${data} === undefined` +
(useDefaults === "empty" ? ` || ${data} === null || ${data} === ""` : "")
`${childData} === undefined` +
(opts.useDefaults === "empty" ? ` || ${childData} === null || ${childData} === ""` : "")
// TODO remove option `useDefaults === "shared"`
const defaultExpr = useDefaults === "shared" ? useDefault : JSON.stringify
gen.if(condition, `${data} = ${defaultExpr(defaultValue)}`)
const defaultExpr = opts.useDefaults === "shared" ? useDefault : JSON.stringify
gen.if(condition, `${childData} = ${defaultExpr(defaultValue)}`)
}
Loading

0 comments on commit 60c4b3c

Please sign in to comment.