fork of hey-api/openapi-ts because I need some additional things
at feat/skip-token 417 lines 14 kB view raw
1import type { PyNode } from './nodes/base'; 2import { PyNodeKind } from './nodes/kinds'; 3 4export interface PyPrinterOptions { 5 indentSize?: number; 6} 7 8export function createPrinter(options?: PyPrinterOptions) { 9 const indentSize = options?.indentSize ?? 4; 10 11 let indentLevel = 0; 12 13 function printComments( 14 parts: Array<string>, 15 lines: ReadonlyArray<string>, 16 indent?: boolean, 17 ): void { 18 if (indent) indentLevel += 1; 19 parts.push(...lines.map((line) => printLine(`# ${line}`))); 20 if (indent) indentLevel -= 1; 21 } 22 23 function printDocstring(docstring: string): Array<string> { 24 const lines = docstring.split('\n'); 25 const parts: Array<string> = []; 26 if (lines.length === 1) { 27 parts.push(printLine(`"""${lines[0]}"""`), ''); 28 } else { 29 parts.push(printLine(`"""`)); 30 parts.push(...lines.map((line) => printLine(line))); 31 parts.push(printLine(`"""`), ''); 32 } 33 return parts; 34 } 35 36 function printLine(line: string): string { 37 if (line === '') return ''; 38 return ' '.repeat(indentLevel * indentSize) + line; 39 } 40 41 function printNode(node: PyNode): string { 42 const parts: Array<string> = []; 43 44 if (node.leadingComments) { 45 printComments(parts, node.leadingComments); 46 } 47 48 let indentTrailingComments = false; 49 50 switch (node.kind) { 51 case PyNodeKind.Assignment: 52 parts.push(printLine(`${printNode(node.target)} = ${printNode(node.value)}`)); 53 break; 54 55 case PyNodeKind.AsyncExpression: 56 parts.push(`async ${printNode(node.expression)}`); 57 break; 58 59 case PyNodeKind.AugmentedAssignment: 60 parts.push( 61 printLine(`${printNode(node.target)} ${node.operator} ${printNode(node.value)}`), 62 ); 63 break; 64 65 case PyNodeKind.AwaitExpression: 66 parts.push(`await ${printNode(node.expression)}`); 67 break; 68 69 case PyNodeKind.BinaryExpression: 70 parts.push(`${printNode(node.left)} ${node.operator} ${printNode(node.right)}`); 71 break; 72 73 case PyNodeKind.Block: 74 indentLevel += 1; 75 if (node.statements.length) { 76 parts.push(...node.statements.map(printNode)); 77 } else { 78 parts.push(printLine('pass')); 79 } 80 indentLevel -= 1; 81 break; 82 83 case PyNodeKind.BreakStatement: 84 parts.push(printLine('break')); 85 break; 86 87 case PyNodeKind.CallExpression: 88 parts.push(`${printNode(node.callee)}(${node.args.map(printNode).join(', ')})`); 89 break; 90 91 case PyNodeKind.ClassDeclaration: { 92 indentTrailingComments = true; 93 if (node.decorators) { 94 parts.push(...node.decorators.map((decorator) => printLine(`@${printNode(decorator)}`))); 95 } 96 const bases = node.baseClasses?.length 97 ? `(${node.baseClasses.map(printNode).join(', ')})` 98 : ''; 99 parts.push(printLine(`class ${node.name}${bases}:`)); 100 if (node.docstring) { 101 indentLevel += 1; 102 parts.push(...printDocstring(node.docstring)); 103 indentLevel -= 1; 104 } 105 parts.push(printNode(node.body)); 106 break; 107 } 108 109 case PyNodeKind.Comment: 110 parts.push(printLine(`# ${node.text}`)); 111 break; 112 113 case PyNodeKind.ContinueStatement: 114 parts.push(printLine('continue')); 115 break; 116 117 case PyNodeKind.DictComprehension: { 118 const asyncPrefix = node.isAsync ? 'async ' : ''; 119 const children: Array<string> = [ 120 `${printNode(node.key)}: ${printNode(node.value)} ${asyncPrefix}for ${printNode(node.target)} in ${printNode(node.iterable)}`, 121 ]; 122 if (node.ifs) { 123 for (const condition of node.ifs) { 124 children.push(`if ${printNode(condition)}`); 125 } 126 } 127 parts.push(`{${children.join(' ')}}`); 128 break; 129 } 130 131 case PyNodeKind.DictExpression: { 132 const entries = node.entries 133 .map(({ key, value }) => `${printNode(key)}: ${printNode(value)}`) 134 .join(', '); 135 parts.push(`{${entries}}`); 136 break; 137 } 138 139 case PyNodeKind.EmptyStatement: 140 parts.push(''); 141 break; 142 143 case PyNodeKind.ExpressionStatement: 144 parts.push(printLine(printNode(node.expression))); 145 break; 146 147 case PyNodeKind.ForStatement: 148 parts.push(printLine(`for ${printNode(node.target)} in ${printNode(node.iterable)}:`)); 149 parts.push(printNode(node.body)); 150 if (node.elseBlock) { 151 parts.push(`${printLine('else:')}`); 152 parts.push(`${printNode(node.elseBlock)}`); 153 } 154 break; 155 156 case PyNodeKind.FStringExpression: { 157 const children = node.parts.map((part) => 158 typeof part === 'string' ? part : `{${printNode(part)}}`, 159 ); 160 parts.push(`f"${children.join('')}"`); 161 break; 162 } 163 164 case PyNodeKind.FunctionDeclaration: { 165 if (node.decorators) { 166 parts.push(...node.decorators.map((decorator) => printLine(`@${printNode(decorator)}`))); 167 } 168 const modifiers = node.modifiers?.map(printNode).join(' ') ?? ''; 169 const defPrefix = modifiers ? `${modifiers} def` : 'def'; 170 const parameters = node.parameters.map((parameter) => { 171 const children: Array<string> = [parameter.name]; 172 if (parameter.annotation) children.push(`: ${printNode(parameter.annotation)}`); 173 if (parameter.defaultValue) children.push(` = ${printNode(parameter.defaultValue)}`); 174 return children.join(''); 175 }); 176 const returnAnnotation = node.returnType ? ` -> ${printNode(node.returnType)}` : ''; 177 parts.push( 178 printLine(`${defPrefix} ${node.name}(${parameters.join(', ')})${returnAnnotation}:`), 179 ); 180 if (node.docstring) { 181 indentLevel += 1; 182 parts.push(...printDocstring(node.docstring)); 183 indentLevel -= 1; 184 } 185 parts.push(printNode(node.body)); 186 break; 187 } 188 189 case PyNodeKind.GeneratorExpression: { 190 const asyncPrefix = node.isAsync ? 'async ' : ''; 191 const children: Array<string> = [ 192 `${printNode(node.element)} ${asyncPrefix}for ${printNode(node.target)} in ${printNode(node.iterable)}`, 193 ]; 194 if (node.ifs) { 195 for (const condition of node.ifs) { 196 children.push(`if ${printNode(condition)}`); 197 } 198 } 199 parts.push(`(${children.join(' ')})`); 200 break; 201 } 202 203 case PyNodeKind.Identifier: 204 parts.push(node.name); 205 break; 206 207 case PyNodeKind.IfStatement: 208 parts.push(printLine(`if ${printNode(node.condition)}:`)); 209 parts.push(`${printNode(node.thenBlock)}`); 210 if (node.elseBlock) { 211 parts.push(`${printLine('else:')}`); 212 parts.push(`${printNode(node.elseBlock)}`); 213 } 214 break; 215 216 case PyNodeKind.ImportStatement: { 217 const fromPrefix = node.isFrom ? `from ${node.module} ` : ''; 218 if (fromPrefix) { 219 if (node.names && node.names.length > 0) { 220 const imports = node.names 221 .map(({ alias, name }) => (alias ? `${name} as ${alias}` : name)) 222 .join(', '); 223 parts.push(printLine(`${fromPrefix}import ${imports}`)); 224 } else { 225 parts.push(printLine(`${fromPrefix}import *`)); 226 } 227 } else { 228 if (node.names && node.names.length > 0) { 229 const imports = node.names 230 .map(({ alias, name }) => (alias ? `${name} as ${alias}` : name)) 231 .join(', '); 232 parts.push(printLine(`import ${imports}`)); 233 } else { 234 parts.push(printLine(`import ${node.module}`)); 235 } 236 } 237 break; 238 } 239 240 case PyNodeKind.LambdaExpression: { 241 const parameters = node.parameters.map((parameter) => { 242 const children: Array<string> = [parameter.name]; 243 if (parameter.annotation) children.push(`: ${printNode(parameter.annotation)}`); 244 if (parameter.defaultValue) children.push(` = ${printNode(parameter.defaultValue)}`); 245 return children.join(''); 246 }); 247 parts.push(`lambda ${parameters.join(', ')}: ${printNode(node.expression)}`); 248 break; 249 } 250 251 case PyNodeKind.ListComprehension: { 252 const asyncPrefix = node.isAsync ? 'async ' : ''; 253 const children: Array<string> = [ 254 `${printNode(node.element)} ${asyncPrefix}for ${printNode(node.target)} in ${printNode(node.iterable)}`, 255 ]; 256 if (node.ifs) { 257 for (const condition of node.ifs) { 258 children.push(`if ${printNode(condition)}`); 259 } 260 } 261 parts.push(`[${children.join(' ')}]`); 262 break; 263 } 264 265 case PyNodeKind.ListExpression: 266 parts.push(`[${node.elements.map(printNode).join(', ')}]`); 267 break; 268 269 case PyNodeKind.Literal: 270 if (typeof node.value === 'string') { 271 parts.push(`"${node.value}"`); 272 } else if (typeof node.value === 'boolean') { 273 parts.push(node.value ? 'True' : 'False'); 274 } else if (node.value === null) { 275 parts.push('None'); 276 } else { 277 parts.push(String(node.value)); 278 } 279 break; 280 281 case PyNodeKind.MemberExpression: 282 parts.push(`${printNode(node.object)}.${printNode(node.member)}`); 283 break; 284 285 case PyNodeKind.RaiseStatement: 286 if (node.expression) { 287 parts.push(printLine(`raise ${printNode(node.expression)}`)); 288 } else { 289 parts.push(printLine('raise')); 290 } 291 break; 292 293 case PyNodeKind.ReturnStatement: 294 if (node.expression) { 295 parts.push(printLine(`return ${printNode(node.expression)}`)); 296 } else { 297 parts.push(printLine('return')); 298 } 299 break; 300 301 case PyNodeKind.SetComprehension: { 302 const asyncPrefix = node.isAsync ? 'async ' : ''; 303 const children: Array<string> = [ 304 `${printNode(node.element)} ${asyncPrefix}for ${printNode(node.target)} in ${printNode(node.iterable)}`, 305 ]; 306 if (node.ifs) { 307 for (const condition of node.ifs) { 308 children.push(`if ${printNode(condition)}`); 309 } 310 } 311 parts.push(`{${children.join(' ')}}`); 312 break; 313 } 314 315 case PyNodeKind.SetExpression: { 316 if (!node.elements.length) { 317 parts.push('set()'); 318 } else { 319 parts.push(`{${node.elements.map(printNode).join(', ')}}`); 320 } 321 break; 322 } 323 324 case PyNodeKind.SourceFile: 325 if (node.docstring) { 326 parts.push(...printDocstring(node.docstring)); 327 } 328 parts.push(...node.statements.map(printNode)); 329 break; 330 331 case PyNodeKind.TryStatement: { 332 parts.push(printLine('try:'), printNode(node.tryBlock)); 333 if (node.exceptClauses) { 334 for (const clause of node.exceptClauses) { 335 const type = clause.exceptionType ? ` ${printNode(clause.exceptionType)}` : ''; 336 const name = clause.exceptionName ? ` as ${printNode(clause.exceptionName)}` : ''; 337 parts.push(printLine(`except${type}${name}:`), printNode(clause.block)); 338 } 339 } 340 if (node.elseBlock) { 341 parts.push(printLine(`else:`), printNode(node.elseBlock)); 342 } 343 if (node.finallyBlock) { 344 parts.push(printLine(`finally:`), printNode(node.finallyBlock)); 345 } 346 break; 347 } 348 349 case PyNodeKind.TupleExpression: { 350 // Single-element tuple needs trailing comma 351 const trailingComma = node.elements.length === 1 ? ',' : ''; 352 parts.push(`(${node.elements.map(printNode).join(', ')}${trailingComma})`); 353 break; 354 } 355 356 case PyNodeKind.WhileStatement: { 357 parts.push(printLine(`while ${printNode(node.condition)}:`)); 358 parts.push(printNode(node.body)); 359 if (node.elseBlock) { 360 parts.push(`${printLine('else:')}`); 361 parts.push(`${printNode(node.elseBlock)}`); 362 } 363 break; 364 } 365 366 case PyNodeKind.WithStatement: { 367 const modifiers = node.modifiers?.map(printNode).join(' ') ?? ''; 368 const withPrefix = modifiers ? `${modifiers} with` : 'with'; 369 const items = node.items 370 .map((item) => 371 item.alias 372 ? `${printNode(item.contextExpr)} as ${printNode(item.alias)}` 373 : printNode(item.contextExpr), 374 ) 375 .join(', '); 376 parts.push(printLine(`${withPrefix} ${items}:`)); 377 parts.push(printNode(node.body)); 378 break; 379 } 380 381 case PyNodeKind.YieldExpression: 382 if (node.value) { 383 parts.push(`yield ${printNode(node.value)}`); 384 } else { 385 parts.push('yield'); 386 } 387 break; 388 389 case PyNodeKind.YieldFromExpression: 390 parts.push(`yield from ${printNode(node.expression)}`); 391 break; 392 393 default: 394 // @ts-expect-error 395 throw new Error(`Unsupported node kind: ${node.kind}`); 396 } 397 398 if (node.trailingComments) { 399 printComments(parts, node.trailingComments, indentTrailingComments); 400 } 401 402 return parts.join('\n'); 403 } 404 405 function printFile(node: PyNode): string { 406 const parts: Array<string> = [printNode(node), '']; 407 return parts.join('\n'); 408 } 409 410 return { 411 printFile, 412 }; 413} 414 415export function printAst(node: PyNode): string { 416 return JSON.stringify(node, null, 2); 417}