Skip to content

Commit

Permalink
Optimize optimizer to avoid deep JSON serialization (fix #422)
Browse files Browse the repository at this point in the history
  • Loading branch information
Boris Cherny committed May 15, 2022
1 parent f943f32 commit 1bab98d
Show file tree
Hide file tree
Showing 11 changed files with 13,901 additions and 52 deletions.
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@
"glob-promise": "^3.4.0",
"is-glob": "^4.0.1",
"json-schema-ref-parser": "^9.0.9",
"json-stringify-safe": "^5.0.1",
"lodash": "^4.17.20",
"minimist": "^1.2.5",
"mkdirp": "^1.0.4",
Expand Down
5 changes: 3 additions & 2 deletions src/generator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {omit} from 'lodash'
import {memoize, omit} from 'lodash'
import {DEFAULT_OPTIONS, Options} from './index'
import {
AST,
Expand Down Expand Up @@ -156,7 +156,7 @@ function declareNamedTypes(ast: AST, options: Options, rootASTName: string, proc
return type
}

function generateType(ast: AST, options: Options): string {
function generateTypeUnmemoized(ast: AST, options: Options): string {
const type = generateRawType(ast, options)

if (options.strictIndexSignatures && ast.keyName === '[k: string]') {
Expand All @@ -165,6 +165,7 @@ function generateType(ast: AST, options: Options): string {

return type
}
export const generateType = memoize(generateTypeUnmemoized)

function generateRawType(ast: AST, options: Options): string {
log('magenta', 'generator', ast)
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ export async function compile(schema: JSONSchema4, name: string, options: Partia
const parsed = parse(normalized, _options)
log('blue', 'parser', time(), '✅ Result:', parsed)

const optimized = optimize(parsed)
const optimized = optimize(parsed, _options)
if (process.env.VERBOSE) {
if (isDeepStrictEqual(parsed, optimized)) {
log('cyan', 'optimizer', time(), '✅ No change')
Expand Down
44 changes: 29 additions & 15 deletions src/optimizer.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import stringify = require('json-stringify-safe')
import {uniqBy} from 'lodash'
import {Options} from '.'
import {generateType} from './generator'
import {AST, T_ANY, T_UNKNOWN} from './types/AST'
import {log} from './utils'

export function optimize(ast: AST, processed = new Set<AST>()): AST {
log('cyan', 'optimizer', ast, processed.has(ast) ? '(FROM CACHE)' : '')

export function optimize(ast: AST, options: Options, processed = new Set<AST>()): AST {
if (processed.has(ast)) {
return ast
}
Expand All @@ -15,7 +14,7 @@ export function optimize(ast: AST, processed = new Set<AST>()): AST {
switch (ast.type) {
case 'INTERFACE':
return Object.assign(ast, {
params: ast.params.map(_ => Object.assign(_, {ast: optimize(_.ast, processed)}))
params: ast.params.map(_ => Object.assign(_, {ast: optimize(_.ast, options, processed)}))
})
case 'INTERSECTION':
case 'UNION':
Expand All @@ -31,25 +30,40 @@ export function optimize(ast: AST, processed = new Set<AST>()): AST {
return T_UNKNOWN
}

// [A (named), A] -> [A (named)]
if (
ast.params.every(_ => {
const a = generateType(omitStandaloneName(_), options)
const b = generateType(omitStandaloneName(ast.params[0]), options)
return a === b
}) &&
ast.params.some(_ => _.standaloneName !== undefined)
) {
log('cyan', 'optimizer', '[A, B, B] -> [A, B]', ast)
ast.params = ast.params.filter(_ => _.standaloneName !== undefined)
}

// [A, B, B] -> [A, B]
const shouldTakeStandaloneNameIntoAccount = ast.params.filter(_ => _.standaloneName).length > 1
const params = uniqBy(
ast.params,
_ => `
${_.type}-
${shouldTakeStandaloneNameIntoAccount ? _.standaloneName : ''}-
${stringify((_ as any).params)}
`
)
const params = uniqBy(ast.params, _ => `${_.standaloneName}:${generateType(_, options)}`)
if (params.length !== ast.params.length) {
log('cyan', 'optimizer', '[A, B, B] -> [A, B]', ast)
ast.params = params
}

return Object.assign(ast, {
params: ast.params.map(_ => optimize(_, processed))
params: ast.params.map(_ => optimize(_, options, processed))
})
default:
return ast
}
}

// TODO: More clearly disambiguate standalone names vs. aliased names instead.
function omitStandaloneName<A extends AST>(ast: A): A {
switch (ast.type) {
case 'ENUM':
return ast
default:
return {...ast, standaloneName: undefined}
}
}
Loading

0 comments on commit 1bab98d

Please sign in to comment.