Skip to content

Commit

Permalink
feat(context): addToContext can return type refs (#1057)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jason Kuhrt authored Jun 17, 2020
1 parent c4ecbcb commit 2b13942
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 26 deletions.
145 changes: 127 additions & 18 deletions src/lib/add-to-context-extractor/extractor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ it('extracts from returned object of primitive values from single call', () => {
Object {
"typeImports": Array [],
"types": Array [
"{ a: number; }",
Object {
"kind": "literal",
"value": "{ a: number; }",
},
],
}
`)
Expand All @@ -54,8 +57,14 @@ it('extracts from returned object of primitive values from multiple calls', () =
Object {
"typeImports": Array [],
"types": Array [
"{ a: number; }",
"{ b: number; }",
Object {
"kind": "literal",
"value": "{ a: number; }",
},
Object {
"kind": "literal",
"value": "{ b: number; }",
},
],
}
`)
Expand All @@ -71,7 +80,10 @@ it('extracts from returned object of referenced primitive value', () => {
Object {
"typeImports": Array [],
"types": Array [
"{ a: number; }",
Object {
"kind": "literal",
"value": "{ a: number; }",
},
],
}
`)
Expand All @@ -88,7 +100,10 @@ it('extracts from returned object of referenced object value', () => {
Object {
"typeImports": Array [],
"types": Array [
"{ foo: { bar: { baz: string; }; }; }",
Object {
"kind": "literal",
"value": "{ foo: { bar: { baz: string; }; }; }",
},
],
}
`)
Expand All @@ -109,7 +124,10 @@ it('extracts from returned object of referenced object with inline type', () =>
Object {
"typeImports": Array [],
"types": Array [
"{ foo: { bar: { baz: string; }; }; }",
Object {
"kind": "literal",
"value": "{ foo: { bar: { baz: string; }; }; }",
},
],
}
`)
Expand Down Expand Up @@ -139,7 +157,10 @@ it('captures required imports information', () => {
},
],
"types": Array [
"{ foo: Foo; }",
Object {
"kind": "literal",
"value": "{ foo: Foo; }",
},
],
}
`)
Expand Down Expand Up @@ -169,7 +190,10 @@ it('detects if a referenced type is not exported', () => {
},
],
"types": Array [
"{ foo: Foo; }",
Object {
"kind": "literal",
"value": "{ foo: Foo; }",
},
],
}
`)
Expand All @@ -186,7 +210,10 @@ it('extracts optionality from props', () => {
Object {
"typeImports": Array [],
"types": Array [
"{ foo: { bar?: { baz?: string; }; }; }",
Object {
"kind": "literal",
"value": "{ foo: { bar?: { baz?: string; }; }; }",
},
],
}
`)
Expand Down Expand Up @@ -216,7 +243,10 @@ it('extracts from type alias', () => {
},
],
"types": Array [
"{ foo: Foo; }",
Object {
"kind": "literal",
"value": "{ foo: Foo; }",
},
],
}
`)
Expand Down Expand Up @@ -246,7 +276,10 @@ it('prop type intersection', () => {
},
],
"types": Array [
"{ foo: Foo & Bar; }",
Object {
"kind": "literal",
"value": "{ foo: Foo & Bar; }",
},
],
}
`)
Expand All @@ -271,7 +304,10 @@ it('prop type aliased intersection', () => {
},
],
"types": Array [
"{ foo: Qux; }",
Object {
"kind": "literal",
"value": "{ foo: Qux; }",
},
],
}
`)
Expand Down Expand Up @@ -301,7 +337,10 @@ it('prop type union', () => {
},
],
"types": Array [
"{ foo: Foo | Bar; }",
Object {
"kind": "literal",
"value": "{ foo: Foo | Bar; }",
},
],
}
`)
Expand All @@ -326,7 +365,10 @@ it('prop type aliased union', () => {
},
],
"types": Array [
"{ foo: Qux; }",
Object {
"kind": "literal",
"value": "{ foo: Qux; }",
},
],
}
`)
Expand Down Expand Up @@ -357,6 +399,61 @@ it.todo('truncates import paths when detected to be an external package')
it.todo('props with union types where one union member is a type reference')
it.todo('props with union intersection types where one intersection member is a type reference')

describe('extracted type refs', () => {
it('an alias', () => {
expect(
extract(`
export type A {}
const a: A
schema.addToContext(req => { return null as A })
`)
).toMatchInlineSnapshot(`
Object {
"typeImports": Array [
Object {
"isExported": true,
"isNode": false,
"modulePath": "/src/a",
"name": "A",
},
],
"types": Array [
Object {
"kind": "ref",
"name": "A",
},
],
}
`)
})
it('an interface', () => {
expect(
extract(`
export interface A {}
const a: A
schema.addToContext(req => { return null as A })
`)
).toMatchInlineSnapshot(`
Object {
"typeImports": Array [
Object {
"isExported": true,
"isNode": false,
"modulePath": "/src/a",
"name": "A",
},
],
"types": Array [
Object {
"kind": "ref",
"name": "A",
},
],
}
`)
})
})

it('dedupes imports', () => {
expect(
extract(`
Expand All @@ -375,8 +472,14 @@ it('dedupes imports', () => {
},
],
"types": Array [
"{ foo: Qux; bar: Qux; }",
"{ mar: Qux; }",
Object {
"kind": "literal",
"value": "{ foo: Qux; bar: Qux; }",
},
Object {
"kind": "literal",
"value": "{ mar: Qux; }",
},
],
}
`)
Expand All @@ -391,7 +494,10 @@ it('does not import array types', () => {
Object {
"typeImports": Array [],
"types": Array [
"{ foo: string[]; }",
Object {
"kind": "literal",
"value": "{ foo: string[]; }",
},
],
}
`)
Expand Down Expand Up @@ -428,7 +534,10 @@ it('support async/await', () => {
Object {
"typeImports": Array [],
"types": Array [
"{ foo: string[]; }",
Object {
"kind": "literal",
"value": "{ foo: string[]; }",
},
],
}
`)
Expand Down
29 changes: 25 additions & 4 deletions src/lib/add-to-context-extractor/extractor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,23 @@ interface TypeImportInfo {
isNode: boolean
}

type A = { a: 2 }

export type ContribTypeRef = { kind: 'ref'; name: string }
export type ContribTypeLiteral = { kind: 'literal'; value: string }
export type ContribType = ContribTypeRef | ContribTypeLiteral

export interface ExtractedContextTypes {
typeImports: TypeImportInfo[]
// types: Record<string, string>[]
types: string[]
types: ContribType[]
}

function contribTypeRef(name: string): ContribTypeRef {
return { kind: 'ref', name }
}

function contribTypeLiteral(value: string): ContribTypeLiteral {
return { kind: 'literal', value }
}

/**
Expand Down Expand Up @@ -67,7 +80,6 @@ export function extractContextTypes(program: ts.Program): ExtractedContextTypes

const expText = exp.getExpression().getText()
const propName = exp.getName()
console.log(expText, propName)

if (expText !== 'schema' || propName !== 'addToContext') {
n.forEachChild(visit)
Expand Down Expand Up @@ -115,7 +127,16 @@ export function extractContextTypes(program: ts.Program): ExtractedContextTypes
tsm.ts.TypeFormatFlags.NoTruncation
)

contextTypeContributions.types.push(contextAdderRetTypeString)
if (unwrappedContextAdderRetType.isInterface() || unwrappedContextAdderRetType.getAliasSymbol()) {
const info = extractTypeImportInfoFromType(unwrappedContextAdderRetType)
if (info) {
typeImportsIndex[info.name] = info
}
contextTypeContributions.types.push(contribTypeRef(contextAdderRetTypeString))
return
}

contextTypeContributions.types.push(contribTypeLiteral(contextAdderRetTypeString))

// search for named references, they will require importing later on
const contextAdderRetProps = unwrappedContextAdderRetType.getProperties()
Expand Down
21 changes: 17 additions & 4 deletions src/lib/add-to-context-extractor/typegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { hardWriteFile } from '../fs'
import * as Layout from '../layout'
import { rootLogger } from '../nexus-logger'
import { createTSProgram } from '../tsc'
import { extractContextTypes, ExtractedContextTypes } from './extractor'
import { ContribType, extractContextTypes, ExtractedContextTypes } from './extractor'

const log = rootLogger.child('addToContextExtractor')

Expand Down Expand Up @@ -39,10 +39,14 @@ export async function generateContextExtractionArtifacts(
* Output the context types to a typegen file.
*/
export async function writeContextTypeGenFile(contextTypes: ExtractedContextTypes) {
const addToContextInterfaces = contextTypes.types
.map((result) => `interface Context ${result}`)
let addToContextInterfaces = contextTypes.types
.map(renderContextInterfaceForExtractedReturnType)
.join('\n\n')

if (addToContextInterfaces.trim() === '') {
addToContextInterfaces = `interface Context {} // none\n\n`
}

const content = codeBlock`
import app from 'nexus'
Expand All @@ -60,7 +64,7 @@ export async function writeContextTypeGenFile(contextTypes: ExtractedContextType
// The context types extracted from the app.
${addToContextInterfaces.length > 0 ? addToContextInterfaces : `interface Context {}`}
${addToContextInterfaces}
`

await hardWriteFile(NEXUS_DEFAULT_RUNTIME_CONTEXT_TYPEGEN_PATH, content)
Expand All @@ -69,3 +73,12 @@ export async function writeContextTypeGenFile(contextTypes: ExtractedContextType
function renderImport(input: { from: string; names: string[] }) {
return `import { ${input.names.join(', ')} } from '${input.from}'`
}

function renderContextInterfaceForExtractedReturnType(contribType: ContribType): string {
switch (contribType.kind) {
case 'literal':
return `interface Context ${contribType.value}`
case 'ref':
return `interface Context extends ${contribType.name} {}`
}
}

0 comments on commit 2b13942

Please sign in to comment.