Skip to content

Commit 1bab98d

Browse files
author
Boris Cherny
committed
Optimize optimizer to avoid deep JSON serialization (fix #422)
1 parent f943f32 commit 1bab98d

File tree

11 files changed

+13901
-52
lines changed

11 files changed

+13901
-52
lines changed

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@
5555
"glob-promise": "^3.4.0",
5656
"is-glob": "^4.0.1",
5757
"json-schema-ref-parser": "^9.0.9",
58-
"json-stringify-safe": "^5.0.1",
5958
"lodash": "^4.17.20",
6059
"minimist": "^1.2.5",
6160
"mkdirp": "^1.0.4",

src/generator.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {omit} from 'lodash'
1+
import {memoize, omit} from 'lodash'
22
import {DEFAULT_OPTIONS, Options} from './index'
33
import {
44
AST,
@@ -156,7 +156,7 @@ function declareNamedTypes(ast: AST, options: Options, rootASTName: string, proc
156156
return type
157157
}
158158

159-
function generateType(ast: AST, options: Options): string {
159+
function generateTypeUnmemoized(ast: AST, options: Options): string {
160160
const type = generateRawType(ast, options)
161161

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

166166
return type
167167
}
168+
export const generateType = memoize(generateTypeUnmemoized)
168169

169170
function generateRawType(ast: AST, options: Options): string {
170171
log('magenta', 'generator', ast)

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ export async function compile(schema: JSONSchema4, name: string, options: Partia
157157
const parsed = parse(normalized, _options)
158158
log('blue', 'parser', time(), '✅ Result:', parsed)
159159

160-
const optimized = optimize(parsed)
160+
const optimized = optimize(parsed, _options)
161161
if (process.env.VERBOSE) {
162162
if (isDeepStrictEqual(parsed, optimized)) {
163163
log('cyan', 'optimizer', time(), '✅ No change')

src/optimizer.ts

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
import stringify = require('json-stringify-safe')
21
import {uniqBy} from 'lodash'
2+
import {Options} from '.'
3+
import {generateType} from './generator'
34
import {AST, T_ANY, T_UNKNOWN} from './types/AST'
45
import {log} from './utils'
56

6-
export function optimize(ast: AST, processed = new Set<AST>()): AST {
7-
log('cyan', 'optimizer', ast, processed.has(ast) ? '(FROM CACHE)' : '')
8-
7+
export function optimize(ast: AST, options: Options, processed = new Set<AST>()): AST {
98
if (processed.has(ast)) {
109
return ast
1110
}
@@ -15,7 +14,7 @@ export function optimize(ast: AST, processed = new Set<AST>()): AST {
1514
switch (ast.type) {
1615
case 'INTERFACE':
1716
return Object.assign(ast, {
18-
params: ast.params.map(_ => Object.assign(_, {ast: optimize(_.ast, processed)}))
17+
params: ast.params.map(_ => Object.assign(_, {ast: optimize(_.ast, options, processed)}))
1918
})
2019
case 'INTERSECTION':
2120
case 'UNION':
@@ -31,25 +30,40 @@ export function optimize(ast: AST, processed = new Set<AST>()): AST {
3130
return T_UNKNOWN
3231
}
3332

33+
// [A (named), A] -> [A (named)]
34+
if (
35+
ast.params.every(_ => {
36+
const a = generateType(omitStandaloneName(_), options)
37+
const b = generateType(omitStandaloneName(ast.params[0]), options)
38+
return a === b
39+
}) &&
40+
ast.params.some(_ => _.standaloneName !== undefined)
41+
) {
42+
log('cyan', 'optimizer', '[A, B, B] -> [A, B]', ast)
43+
ast.params = ast.params.filter(_ => _.standaloneName !== undefined)
44+
}
45+
3446
// [A, B, B] -> [A, B]
35-
const shouldTakeStandaloneNameIntoAccount = ast.params.filter(_ => _.standaloneName).length > 1
36-
const params = uniqBy(
37-
ast.params,
38-
_ => `
39-
${_.type}-
40-
${shouldTakeStandaloneNameIntoAccount ? _.standaloneName : ''}-
41-
${stringify((_ as any).params)}
42-
`
43-
)
47+
const params = uniqBy(ast.params, _ => `${_.standaloneName}:${generateType(_, options)}`)
4448
if (params.length !== ast.params.length) {
4549
log('cyan', 'optimizer', '[A, B, B] -> [A, B]', ast)
4650
ast.params = params
4751
}
4852

4953
return Object.assign(ast, {
50-
params: ast.params.map(_ => optimize(_, processed))
54+
params: ast.params.map(_ => optimize(_, options, processed))
5155
})
5256
default:
5357
return ast
5458
}
5559
}
60+
61+
// TODO: More clearly disambiguate standalone names vs. aliased names instead.
62+
function omitStandaloneName<A extends AST>(ast: A): A {
63+
switch (ast.type) {
64+
case 'ENUM':
65+
return ast
66+
default:
67+
return {...ast, standaloneName: undefined}
68+
}
69+
}

0 commit comments

Comments
 (0)