|
1 | 1 | import { CodeMaker } from 'codemaker'; |
2 | | -import { Assembly, Type, Submodule as JsiiSubmodule } from 'jsii-reflect'; |
3 | | -import { join } from 'path'; |
| 2 | +import { Type, Submodule as JsiiSubmodule } from 'jsii-reflect'; |
| 3 | +import { EmitContext } from './emit-context'; |
4 | 4 | import { GoClass, Enum, Interface } from './types'; |
| 5 | +import { findTypeInTree, goModuleName, flatMap } from './util'; |
5 | 6 |
|
6 | | -type ModuleType = Interface | Enum | GoClass; |
7 | | -type ModuleTypes = ModuleType[]; |
8 | | - |
9 | | -// TODO: Make this a class? |
10 | | -export interface Module { |
11 | | - root: RootModule; |
12 | | - packageName: string; |
13 | | - moduleName: string; |
14 | | - filePath: string; |
15 | | - file: string; |
16 | | - submodules: Submodule[]; |
17 | | - types: ModuleTypes; |
18 | | - hasSubmodules(): boolean; |
19 | | -} |
20 | | - |
21 | | -function buildSubmodules( |
22 | | - root: RootModule, |
23 | | - parent: Module, |
24 | | - submodules: readonly JsiiSubmodule[], |
25 | | -): Submodule[] { |
26 | | - return submodules.map((sm) => new Submodule(root, parent, sm)); |
27 | | -} |
28 | | - |
29 | | -function unknownType(type: Type): never { |
30 | | - throw new Error(`Type: ${type.name} is not an interface, enum, or class`); |
31 | | -} |
32 | | - |
33 | | -function buildModuleTypes(parent: Module, types: readonly Type[]): ModuleTypes { |
34 | | - return types.map( |
35 | | - (type: Type): ModuleType => { |
36 | | - if (type.isInterfaceType()) { |
37 | | - return new Interface(parent, type); |
38 | | - } else if (type.isClassType()) { |
39 | | - return new GoClass(parent, type); |
40 | | - } else if (type.isEnumType()) { |
41 | | - return new Enum(parent, type); |
42 | | - } |
43 | | - return unknownType(type); |
44 | | - }, |
45 | | - ); |
46 | | -} |
47 | | - |
48 | | -function findTypeInTree(module: Module, fqn: string): ModuleType | undefined { |
49 | | - const result = module.types.find((t) => t.type.fqn === fqn); |
50 | | - |
51 | | - if (result) { |
52 | | - return result; |
53 | | - } else if (module.hasSubmodules()) { |
54 | | - return module.submodules.reduce((accum: ModuleType | undefined, sm) => { |
55 | | - return accum || findTypeInTree(sm, fqn); |
56 | | - }, undefined); |
57 | | - } |
58 | | - |
59 | | - return undefined; |
60 | | -} |
61 | | - |
| 7 | +// JSII golang runtime module name |
62 | 8 | const JSII_MODULE_NAME = 'github.com/aws-cdk/jsii/jsii'; |
63 | | -export abstract class ModuleFile { |
64 | | - public constructor(public readonly file: string) {} |
65 | | - public open(code: CodeMaker): void { |
66 | | - code.openFile(this.file); |
67 | | - } |
68 | 9 |
|
69 | | - public close(code: CodeMaker): void { |
70 | | - code.closeFile(this.file); |
71 | | - } |
72 | | -} |
| 10 | +export type ModuleType = Interface | Enum | GoClass; |
| 11 | +type ModuleTypes = ModuleType[]; |
73 | 12 |
|
74 | | -export class RootModule extends ModuleFile implements Module { |
75 | | - private readonly assembly: Assembly; |
76 | | - public readonly packageName: string; |
77 | | - public readonly moduleName: string; |
| 13 | +/* |
| 14 | + * Module represents a single `.go` source file within a package. This can be the root package file or a submodule |
| 15 | + */ |
| 16 | +export abstract class Module { |
| 17 | + public readonly root: Module; |
78 | 18 | public readonly file: string; |
79 | | - public readonly filePath: string; |
80 | | - public readonly root: RootModule; |
81 | | - |
82 | | - public constructor(assembly: Assembly) { |
83 | | - const packageName = assembly.name |
84 | | - .replace('@', '') |
85 | | - .replace(/[^a-z0-9_.]/gi, ''); |
86 | | - const filePath = join(...packageName.split('.')); |
87 | | - const file = `${filePath}.go`; |
88 | | - super(file); |
89 | | - |
90 | | - this.assembly = assembly; |
91 | | - this.root = this; |
92 | | - this.packageName = packageName; |
93 | | - // moduleName == packageName for root; |
94 | | - this.moduleName = packageName; |
95 | | - this.filePath = filePath; |
96 | | - this.file = file; |
97 | | - } |
| 19 | + public readonly submodules: Submodule[]; |
| 20 | + public readonly types: ModuleTypes; |
| 21 | + // public readonly dependencies: Module[]; |
98 | 22 |
|
99 | | - public get types(): ModuleTypes { |
100 | | - return buildModuleTypes(this, Object.values(this.assembly.types)); |
101 | | - } |
| 23 | + public constructor( |
| 24 | + private readonly typeSpec: readonly Type[], |
| 25 | + private readonly submoduleSpec: readonly JsiiSubmodule[], |
| 26 | + public readonly moduleName: string, |
| 27 | + public readonly filePath: string, |
| 28 | + // If no root is provided, this module is the root |
| 29 | + root?: Module, |
| 30 | + ) { |
| 31 | + this.file = `${filePath}.go`; |
| 32 | + this.root = root || this; |
| 33 | + this.submodules = this.submoduleSpec.map( |
| 34 | + (sm) => new Submodule(this.root, this, sm), |
| 35 | + ); |
102 | 36 |
|
103 | | - public get submodules(): Submodule[] { |
104 | | - return buildSubmodules(this, this, this.assembly.submodules); |
| 37 | + this.types = this.typeSpec.map( |
| 38 | + (type: Type): ModuleType => { |
| 39 | + if (type.isInterfaceType()) { |
| 40 | + return new Interface(this, type); |
| 41 | + } else if (type.isClassType()) { |
| 42 | + return new GoClass(this, type); |
| 43 | + } else if (type.isEnumType()) { |
| 44 | + return new Enum(this, type); |
| 45 | + } |
| 46 | + throw new Error( |
| 47 | + `Type: ${type.name} with kind ${type.kind} is not a supported type`, |
| 48 | + ); |
| 49 | + }, |
| 50 | + ); |
105 | 51 | } |
106 | 52 |
|
107 | | - public get dependencies(): Set<Module> { |
108 | | - return new Set( |
109 | | - this.types |
110 | | - .reduce( |
111 | | - (accum: Module[], t: ModuleType) => [...accum, ...t.dependencies], |
112 | | - [], |
113 | | - ) |
114 | | - .filter((mod) => mod.packageName !== this.packageName), |
115 | | - ); |
| 53 | + /* |
| 54 | + * Modules that types within this module reference |
| 55 | + */ |
| 56 | + public get dependencies(): Module[] { |
| 57 | + return flatMap( |
| 58 | + this.types, |
| 59 | + (t: ModuleType): Module[] => t.dependencies, |
| 60 | + ).filter((mod) => mod.moduleName !== this.moduleName); |
116 | 61 | } |
117 | 62 |
|
118 | | - public hasSubmodules(): boolean { |
119 | | - return Boolean(this.submodules.length); |
| 63 | + /* |
| 64 | + * The module names of this modules dependencies. Used for import statements |
| 65 | + */ |
| 66 | + public get dependencyImports(): Set<string> { |
| 67 | + return new Set(this.dependencies.map((mod) => mod.moduleName)); |
120 | 68 | } |
121 | 69 |
|
| 70 | + /* |
| 71 | + * Search for a type with a `fqn` within this. Searches all Children modules as well. |
| 72 | + */ |
122 | 73 | public findType(fqn: string): ModuleType | undefined { |
123 | 74 | return findTypeInTree(this, fqn); |
124 | 75 | } |
125 | 76 |
|
126 | | - public emit(code: CodeMaker): void { |
127 | | - this.open(code); |
128 | | - code.line(`package ${this.packageName}`); |
| 77 | + public emit(context: EmitContext): void { |
| 78 | + const { code } = context; |
| 79 | + code.openFile(this.file); |
| 80 | + this.emitHeader(code); |
| 81 | + this.emitImports(code); |
| 82 | + this.emitTypes(code); |
| 83 | + code.closeFile(this.file); |
| 84 | + |
| 85 | + this.emitSubmodules(context); |
| 86 | + } |
| 87 | + |
| 88 | + private emitHeader(code: CodeMaker) { |
| 89 | + code.line(`package ${this.moduleName}`); |
129 | 90 | code.line(); |
| 91 | + } |
130 | 92 |
|
131 | | - code.line('import ('); |
132 | | - code.indent(); |
| 93 | + private emitImports(code: CodeMaker) { |
| 94 | + code.open('import ('); |
133 | 95 | code.line(`"${JSII_MODULE_NAME}"`); |
134 | | - if (this.dependencies.size > 0) { |
135 | | - this.dependencies.forEach((mod) => { |
136 | | - if (mod.packageName !== this.packageName) { |
137 | | - code.line(`"${mod.packageName}"`); |
138 | | - } |
139 | | - }); |
140 | | - } |
141 | | - code.unindent(); |
142 | | - code.line(')'); |
| 96 | + this.dependencyImports.forEach((modName) => { |
| 97 | + // If the module is the same as the current one being written, don't emit an import statement |
| 98 | + if (modName !== this.moduleName) { |
| 99 | + code.line(`"${modName}"`); |
| 100 | + } |
| 101 | + }); |
| 102 | + code.close(')'); |
143 | 103 | code.line(); |
144 | | - |
145 | | - this.emitTypes(code); |
146 | | - this.close(code); |
147 | | - |
148 | | - this.emitSubmodules(code); |
149 | 104 | } |
150 | 105 |
|
151 | | - public emitTypes(code: CodeMaker) { |
152 | | - Object.values(this.types).forEach((type) => { |
153 | | - type.emit(code); |
| 106 | + public emitSubmodules(context: EmitContext) { |
| 107 | + this.submodules.forEach((submodule) => { |
| 108 | + submodule.emit(context); |
154 | 109 | }); |
155 | 110 | } |
156 | 111 |
|
157 | | - public emitSubmodules(code: CodeMaker) { |
158 | | - this.submodules.forEach((submodule) => { |
159 | | - submodule.emit(code); |
| 112 | + private emitTypes(code: CodeMaker) { |
| 113 | + this.types.forEach((type) => { |
| 114 | + type.emit(code); |
160 | 115 | }); |
161 | 116 | } |
162 | 117 | } |
163 | 118 |
|
164 | | -export class Submodule extends ModuleFile implements Module { |
165 | | - private readonly assembly: JsiiSubmodule; |
166 | | - public readonly packageName: string; |
167 | | - public readonly moduleName: string; |
168 | | - public readonly file: string; |
169 | | - public readonly filePath: string; |
| 119 | +export class Submodule extends Module { |
170 | 120 | public readonly parent: Module; |
171 | | - public readonly root: RootModule; |
172 | 121 |
|
173 | | - public constructor( |
174 | | - root: RootModule, |
175 | | - parent: Module, |
176 | | - submodule: JsiiSubmodule, |
177 | | - ) { |
178 | | - const moduleName = submodule.name |
179 | | - .replace('@', '') |
180 | | - .replace(/[^a-z0-9]/gi, ''); |
181 | | - const packageName = moduleName; |
| 122 | + public constructor(root: Module, parent: Module, assembly: JsiiSubmodule) { |
| 123 | + const moduleName = goModuleName(assembly.name); |
182 | 124 | const filePath = `${parent.filePath}/${moduleName}`; |
183 | | - const file = `${filePath}.go`; |
184 | | - super(file); |
185 | | - |
186 | | - this.assembly = submodule; |
187 | | - this.root = root; |
188 | | - this.parent = parent; |
189 | | - this.packageName = packageName; |
190 | | - this.moduleName = moduleName; |
191 | | - this.filePath = filePath; |
192 | | - this.file = file; |
193 | | - } |
194 | | - |
195 | | - public get types(): ModuleTypes { |
196 | | - return buildModuleTypes(this, this.assembly.types); |
197 | | - } |
198 | | - |
199 | | - public get submodules(): Submodule[] { |
200 | | - return buildSubmodules(this.root, this, this.assembly.submodules); |
201 | | - } |
202 | | - |
203 | | - public hasSubmodules(): boolean { |
204 | | - return Boolean(this.submodules.length); |
205 | | - } |
206 | 125 |
|
207 | | - public get dependencies(): Set<Module> { |
208 | | - return new Set( |
209 | | - this.types |
210 | | - .reduce( |
211 | | - (accum: Module[], t: ModuleType) => [...accum, ...t.dependencies], |
212 | | - [], |
213 | | - ) |
214 | | - .filter((mod) => mod.packageName !== this.packageName), |
215 | | - ); |
216 | | - } |
217 | | - |
218 | | - public emit(code: CodeMaker): void { |
219 | | - this.open(code); |
220 | | - code.line(`package ${this.packageName}`); |
221 | | - code.line(); |
222 | | - |
223 | | - code.line('import ('); |
224 | | - code.indent(); |
225 | | - code.line(`"${JSII_MODULE_NAME}"`); |
226 | | - if (this.dependencies.size > 0) { |
227 | | - this.dependencies.forEach((mod) => { |
228 | | - if (mod.packageName !== this.packageName) { |
229 | | - code.line(`"${mod.packageName}"`); |
230 | | - } |
231 | | - }); |
232 | | - } |
233 | | - code.unindent(); |
234 | | - code.line(')'); |
235 | | - code.line(); |
236 | | - |
237 | | - this.emitTypes(code); |
238 | | - this.close(code); |
| 126 | + super(assembly.types, assembly.submodules, moduleName, filePath, root); |
239 | 127 |
|
240 | | - this.emitSubmodules(code); |
241 | | - } |
242 | | - |
243 | | - public emitSubmodules(code: CodeMaker) { |
244 | | - this.submodules.forEach((submodule) => { |
245 | | - submodule.emit(code); |
246 | | - }); |
247 | | - } |
248 | | - |
249 | | - private emitTypes(code: CodeMaker): void { |
250 | | - this.types.forEach((type) => { |
251 | | - type.emit(code); |
252 | | - }); |
| 128 | + this.parent = parent; |
253 | 129 | } |
254 | 130 | } |
0 commit comments