Skip to content

Commit

Permalink
fix: Typescript SDK generator import logic (#547)
Browse files Browse the repository at this point in the history
- Some assumptions about types that will **always** be imported was wrong, so now some sdk-rtl dependencies are specifically tracked for importing
- also moved the `IDictionary<T>` declaration to `@looker/sdk-rtl` where it belongs
- also logging a bit more of the generation process
- introduced `ICodeGen.reset()` to reset tracking variables before generating methods, streams, and models
  • Loading branch information
jkaster authored Apr 1, 2021
1 parent e55086c commit c5aa033
Show file tree
Hide file tree
Showing 17 changed files with 24,401 additions and 44,301 deletions.
2 changes: 1 addition & 1 deletion packages/sdk-codegen-scripts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ It has node dependencies, so it cannot be used in the browser.

## Scripts

* [sdkGen.ts](src/sdkGen.ts) is the script for the Looker SDK code generator. Run `yarn gen -h` to see generation options.
* [legacy.ts](src/legacy.ts) for the OpenAPI legacy code generator
* [sdkGen.ts](src/sdkGen.ts) is the entry point for the Looker SDK code generator
* [specConvert.ts](src/specConvert.ts) converts a swagger (OpenAPI 2.x) file to OpenAPI 3.x
* [yamlToJson.ts](src/yamlToJson.ts) converts a `YAML` file to a pretty-printed `JSON` file
3 changes: 3 additions & 0 deletions packages/sdk-codegen-scripts/src/sdkGen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ export const writeCodeFile = (fileName: string, content: string): string => {
continue
}
log(`generating ${language} from ${props.base_url} ${api} ...`)
log(`generating ${api} methods ...`)

// Generate standard method declarations
const sdk = new MethodGenerator(apiModel, gen)
Expand All @@ -94,11 +95,13 @@ export const writeCodeFile = (fileName: string, content: string): string => {

if (gen.willItStream) {
// Generate streaming method declarations
log(`generating ${api} streaming methods ...`)
const s = new StreamGenerator(apiModel, gen)
const output = s.render(gen.indentStr)
writeCodeFile(gen.sdkFileName(`streams`), output)
}

log(`generating ${api} models ...`)
const types = new TypeGenerator(apiModel, gen)
output = types.render('')
writeCodeFile(gen.sdkFileName(`models`), output)
Expand Down
2 changes: 2 additions & 0 deletions packages/sdk-codegen-scripts/src/sdkGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ export class MethodGenerator extends Generator<Models.IApiModel> {
}

render(indent: string) {
this.codeFormatter.reset()
const items: string[] = []
// reset refcounts for ALL types so dynamic import statement will work
Object.values(this.model.types).forEach((type) => (type.refCount = 0))
Expand Down Expand Up @@ -171,6 +172,7 @@ export class StreamGenerator extends MethodGenerator {

export class TypeGenerator extends Generator<Models.IApiModel> {
render(indent: string) {
this.codeFormatter.reset()
const items: string[] = []
Object.values(this.model.types).forEach((type) => {
if (!(type instanceof Models.IntrinsicType)) {
Expand Down
17 changes: 17 additions & 0 deletions packages/sdk-codegen/src/codeGen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,11 @@ export interface ICodeGen {
/** Do type declarations use a class definition */
useModelClassForTypes: boolean

/**
* Resets the generator for a new emission
*/
reset(): void

/**
* Quote a string value for the language
* @param value to quote
Expand Down Expand Up @@ -702,6 +707,11 @@ export abstract class CodeGen implements ICodeGen {
}
}

/** base level reset does nothing */
reset() {
// eslint-disable-next-line @typescript-eslint/no-empty-function
}

/**
* Returns true if the SDK supports multiple API versions of models
* @returns True if multi-API is supported
Expand Down Expand Up @@ -1190,6 +1200,13 @@ export abstract class CodeGen implements ICodeGen {
return items
}

/**
* Gets the type mapping to use for generation
*
* Also tracks refCounts for the type for generators that need explicit type imports
*
* @param type to map for generation
*/
typeMap(type: IType): IMappedType {
type.refCount++ // increment refcount
return { default: this.nullStr || '', name: type.name || '' }
Expand Down
59 changes: 41 additions & 18 deletions packages/sdk-codegen/src/typescript.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,20 +70,41 @@ export class TypescriptGen extends CodeGen {
willItStream = true
useNamedParameters = false
useNamedArguments = false
/** Track special imports of sdk-rtl */
rtlNeeds = new Set<string>()

reset() {
this.rtlNeeds = new Set<string>()
}

sdkFileName(baseFileName: string) {
return this.fileName(`${this.versions?.spec.key}/${baseFileName}`)
}

/** lists all special sdk-rtl import types encountered */
rtlImports() {
let rtl = Array.from(this.rtlNeeds).join(', ')
if (rtl) {
rtl += ', '
}
return rtl
}

/** creates a full @looker/sdk-rtl import statement if one is required */
rtlImportStatement() {
const rtl = this.rtlImports()
return rtl ? `\nimport { ${rtl} } from '@looker/sdk-rtl'\n` : ''
}

methodsPrologue(_indent: string) {
return `
import { APIMethods, DelimArray, IAuthSession, ITransportSettings, encodeParam } from '@looker/sdk-rtl'
import { ${this.rtlImports()}APIMethods, IAuthSession, ITransportSettings, encodeParam } from '@looker/sdk-rtl'
/**
* ${this.warnEditing()}
*
*/
import { sdkVersion } from '../constants'
import { IDictionary, ${this.typeNames().join(', ')} } from './models'
import { ${this.typeNames().join(', ')} } from './models'
export class ${this.packageName} extends APIMethods {
static readonly ApiVersion = '${this.apiVersion}'
Expand All @@ -102,13 +123,13 @@ export class ${this.packageName} extends APIMethods {
streamsPrologue(_indent: string): string {
return `
import { Readable } from 'readable-stream'
import { APIMethods, IAuthSession, DelimArray, ITransportSettings, encodeParam } from '@looker/sdk-rtl'
import { ${this.rtlImports()}APIMethods, IAuthSession, ITransportSettings, encodeParam } from '@looker/sdk-rtl'
/**
* ${this.warnEditing()}
*
*/
import { sdkVersion } from '../constants'
import { IDictionary, ${this.typeNames(false).join(', ')} } from './models'
import { ${this.typeNames().join(', ')} } from './models'
export class ${this.packageName}Stream extends APIMethods {
static readonly ApiVersion = '${this.apiVersion}'
Expand All @@ -128,17 +149,11 @@ export class ${this.packageName}Stream extends APIMethods {
}

modelsPrologue(_indent: string) {
return `
import { DelimArray, Url } from '@looker/sdk-rtl'
return `${this.rtlImportStatement()}
/*
* ${this.warnEditing()}
*/
export interface IDictionary<T> {
[key: string]: T
}
`
}

Expand Down Expand Up @@ -318,7 +333,16 @@ export interface IDictionary<T> {
return `'${name}'`
}

/**
* Get the language's type name for generation
*
* Also refcounts the type
*
* @param type to name
* @private
*/
private typeName(type: IType) {
type.refCount++
if (type.customType && !(type instanceof EnumType)) {
return this.reserve(`I${type.name}`)
}
Expand Down Expand Up @@ -459,14 +483,9 @@ export interface IDictionary<T> {
}

// TODO avoid duplicate code
typeNames(countError = true) {
typeNames() {
const names: string[] = []
if (!this.api) return names
if (countError) {
this.api.types.Error.refCount++
} else {
this.api.types.Error.refCount = 0
}
const types = this.api.types
Object.values(types)
.filter((type) => type.refCount > 0 && !type.intrinsic)
Expand Down Expand Up @@ -510,11 +529,13 @@ export interface IDictionary<T> {
name: `${map.name}[]`,
}
case 'HashType':
this.rtlNeeds.add('IDictionary')
return {
default: '{}',
name: `IDictionary<${map.name}>`,
}
case 'DelimArrayType':
this.rtlNeeds.add('DelimArray')
return {
default: '',
name: `DelimArray<${map.name}>`,
Expand All @@ -531,7 +552,9 @@ export interface IDictionary<T> {
}

if (type.name) {
return tsTypes[type.name] || { default: '', name: this.typeName(type) } // No null default for complex types
const mapped = tsTypes[type.name]
if (mapped && mapped.name === 'Url') this.rtlNeeds.add(mapped.name)
return mapped || { default: '', name: this.typeName(type) } // No null default for complex types
} else {
throw new Error('Cannot output a nameless type.')
}
Expand Down
5 changes: 5 additions & 0 deletions packages/sdk-rtl/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ export const unquote = (value: string | undefined | null): string => {
*/
export type Url = string

/** Documented type to clarify SDK hash types */
export interface IDictionary<T> {
[key: string]: T
}

/**
* Documented type alias for password spec
*/
Expand Down
4 changes: 2 additions & 2 deletions packages/sdk/src/3.1/methods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@
*/

import {
APIMethods,
DelimArray,
IDictionary,
APIMethods,
IAuthSession,
ITransportSettings,
encodeParam,
Expand All @@ -41,7 +42,6 @@ import {
*/
import { sdkVersion } from '../constants'
import {
IDictionary,
IAccessToken,
IApiSession,
IApiVersion,
Expand Down
6 changes: 1 addition & 5 deletions packages/sdk/src/3.1/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,12 @@
* 302 API models: 188 Spec, 46 Request, 51 Write, 17 Enum
*/

import { DelimArray, Url } from '@looker/sdk-rtl'
import { IDictionary, Url, DelimArray } from '@looker/sdk-rtl'

/*
* NOTE: Do not edit this file generated by Looker SDK Codegen for Looker 21.4 API 3.1
*/

export interface IDictionary<T> {
[key: string]: T
}

export interface IAccessToken {
/**
* Access Token used for API calls (read-only)
Expand Down
4 changes: 2 additions & 2 deletions packages/sdk/src/3.1/streams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@

import { Readable } from 'readable-stream'
import {
DelimArray,
IDictionary,
APIMethods,
IAuthSession,
DelimArray,
ITransportSettings,
encodeParam,
} from '@looker/sdk-rtl'
Expand All @@ -42,7 +43,6 @@ import {
*/
import { sdkVersion } from '../constants'
import {
IDictionary,
IAccessToken,
IApiSession,
IApiVersion,
Expand Down
4 changes: 2 additions & 2 deletions packages/sdk/src/4.0/methods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@
*/

import {
APIMethods,
DelimArray,
IDictionary,
APIMethods,
IAuthSession,
ITransportSettings,
encodeParam,
Expand All @@ -41,7 +42,6 @@ import {
*/
import { sdkVersion } from '../constants'
import {
IDictionary,
IAccessToken,
IApiSession,
IApiVersion,
Expand Down
6 changes: 1 addition & 5 deletions packages/sdk/src/4.0/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,12 @@
* 329 API models: 205 Spec, 50 Request, 56 Write, 18 Enum
*/

import { DelimArray, Url } from '@looker/sdk-rtl'
import { IDictionary, Url, DelimArray } from '@looker/sdk-rtl'

/*
* NOTE: Do not edit this file generated by Looker SDK Codegen for Looker 21.4 API 4.0
*/

export interface IDictionary<T> {
[key: string]: T
}

export interface IAccessToken {
/**
* Access Token used for API calls (read-only)
Expand Down
4 changes: 2 additions & 2 deletions packages/sdk/src/4.0/streams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@

import { Readable } from 'readable-stream'
import {
DelimArray,
IDictionary,
APIMethods,
IAuthSession,
DelimArray,
ITransportSettings,
encodeParam,
} from '@looker/sdk-rtl'
Expand All @@ -42,7 +43,6 @@ import {
*/
import { sdkVersion } from '../constants'
import {
IDictionary,
IAccessToken,
IApiSession,
IApiVersion,
Expand Down
Loading

0 comments on commit c5aa033

Please sign in to comment.