UNPKG

vite-plugin-react18-pages

Version:

<p> <a href="https://www.npmjs.com/package/vite-plugin-react-pages" target="_blank" rel="noopener"><img src="https://img.shields.io/npm/v/vite-plugin-react-pages.svg" alt="npm package" /></a> </p>

170 lines (150 loc) 5.66 kB
import * as ts from 'typescript' import { TsInterfaceInfo, TsInterfacePropertyInfo, } from '../../../../clientTypes' const defaultTsConfig: ts.CompilerOptions = { moduleResolution: ts.ModuleResolutionKind.NodeJs, } export function collectInterfaceInfo( fileName: string, exportName: string, options: ts.CompilerOptions = defaultTsConfig ): TsInterfaceInfo { // Build a program using the set of root file names in fileNames let program = ts.createProgram([fileName], options) // Get the checker, we will use it to find more about classes let checker = program.getTypeChecker() const sourceFile = program.getSourceFile(fileName)! // inspired by // https://github.com/microsoft/rushstack/blob/6ca0cba723ad8428e6e099f12715ce799f29a73f/apps/api-extractor/src/analyzer/ExportAnalyzer.ts#L702 // and https://stackoverflow.com/a/58885450 const fileSymbol = checker.getSymbolAtLocation(sourceFile) if (!fileSymbol || !fileSymbol.exports) { throw new Error(`unexpected fileSymbol`) } const escapedExportName = ts.escapeLeadingUnderscores(exportName) const exportSymbol = fileSymbol.exports.get(escapedExportName) if (!exportSymbol) { throw new Error(`Named export ${exportName} is not found in file`) } const sourceDeclareSymbol = getAliasedSymbolIfNecessary(exportSymbol) const sourceDeclare = sourceDeclareSymbol.declarations?.[0] if (!sourceDeclare) { throw new Error(`Can't find sourceDeclare for ${exportName}`) } const interfaceInfo = collectInterfaceInfo(sourceDeclare, sourceDeclareSymbol) return interfaceInfo function getAliasedSymbolIfNecessary(symbol: ts.Symbol) { if ((symbol.flags & ts.SymbolFlags.Alias) !== 0) return checker.getAliasedSymbol(symbol) return symbol } function collectInterfaceInfo(node: ts.Declaration, symbol: ts.Symbol) { if (!ts.isInterfaceDeclaration(node)) throw new Error(`target is not an InterfaceDeclaration`) const type = checker.getTypeAtLocation(node) if (!symbol) throw new Error(`can't find symbol`) const name = node.name.getText() const commentText = getComment(node, node.getSourceFile().getFullText()) ?? '' const description = ts.displayPartsToString( symbol.getDocumentationComment(checker) ) const propertiesInfo: TsInterfacePropertyInfo[] = [] // extract property info symbol.members?.forEach((symbol) => { const name = symbol.name const declaration = symbol.valueDeclaration if ( !( declaration && (ts.isPropertySignature(declaration) || ts.isMethodSignature(declaration)) ) ) { // known member in interface symbol // that we don't handle if (symbol.getFlags() & ts.SymbolFlags.TypeParameter) return console.warn( `unexpected declaration type in interface. name: ${name}, kind: ${ ts.SyntaxKind[declaration?.kind as any] }` ) return } const commentText = getComment(declaration, declaration.getSourceFile().getFullText()) ?? '' const typeText = declaration.type?.getFullText() ?? '' const description = ts.displayPartsToString( symbol.getDocumentationComment(checker) ) const isOptional = !!(symbol.getFlags() & ts.SymbolFlags.Optional) // get defaultValue from jsDocTags const jsDocTags = symbol.getJsDocTags() const defaultValueTag = jsDocTags.find( (t) => t.name === 'defaultValue' || 'default' ) const defaultValue = defaultValueTag?.text?.[0].text propertiesInfo.push({ name, // commentText, type: typeText, description, defaultValue, optional: isOptional, // fullText: declaration.getFullText(), }) }) const interfaceInfo: TsInterfaceInfo = { name, // commentText, description, properties: propertiesInfo, // fullText: node.getFullText(), } return interfaceInfo } /** True if this is visible outside this file, false otherwise */ function isNodeExported(node: ts.Node): boolean { return ( (ts.getCombinedModifierFlags(node as ts.Declaration) & ts.ModifierFlags.Export) !== 0 && !!node.parent && node.parent.kind === ts.SyntaxKind.SourceFile ) } } function getJSDocCommentRanges( node: ts.Node, text: string ): ts.CommentRange[] | undefined { // Compiler internal: // https://github.com/microsoft/TypeScript/blob/66ecfcbd04b8234855a673adb85e5cff3f8458d4/src/compiler/utilities.ts#L1202 return (ts as any).getJSDocCommentRanges.apply(ts, arguments) } function getComment(declaration: ts.Declaration, sourceFileFullText: string) { const ranges = getJSDocCommentRanges(declaration, sourceFileFullText) if (!ranges || !ranges.length) return const range = ranges[ranges.length - 1] if (!range) return const text = sourceFileFullText.slice(range.pos, range.end) return text } /** * ref: * * https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API * * https://stackoverflow.com/questions/59838013/how-can-i-use-the-ts-compiler-api-to-find-where-a-variable-was-defined-in-anothe * * https://stackoverflow.com/questions/60249275/typescript-compiler-api-generate-the-full-properties-arborescence-of-a-type-ide * * https://stackoverflow.com/questions/47429792/is-it-possible-to-get-comments-as-nodes-in-the-ast-using-the-typescript-compiler * * Instructions of learning ts compiler: * https://stackoverflow.com/a/58885450 * * https://learning-notes.mistermicheels.com/javascript/typescript/compiler-api/ */