UNPKG

@v19i/openapi-enum-arrays

Version:

A @hey-api/openapi-ts plugin that generates typed enum arrays with intelligent conflict resolution

251 lines (250 loc) 9.85 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.EnumParser = void 0; const semantic_naming_1 = require("./semantic-naming"); /** * Handles line processing, comments, and whitespace filtering */ class TypeScriptTokenizer { shouldSkipLine(line) { if (!line) return true; const commentPrefixes = ["//", "*", "/*", "/**"]; return commentPrefixes.some((prefix) => line.startsWith(prefix)); } cleanLine(line) { return line.trim(); } countOccurrences(text, character) { return (text.match(new RegExp(`\\${character}`, "g")) || []).length; } } /** * Tracks nesting depth and builds context paths */ class ScopeTracker { contextPath = []; nestingLevel = 0; isInTypeDefinition = false; startTypeDefinition(typeName) { this.contextPath = [typeName]; this.nestingLevel = 0; this.isInTypeDefinition = true; } enterNestedObject(propertyName, braceCount) { this.contextPath.push(propertyName); this.nestingLevel += braceCount; } exitScope(braceCount) { this.nestingLevel -= braceCount; for (let i = 0; i < braceCount; i++) { if (this.contextPath.length > 1) { this.contextPath.pop(); } } if (this.nestingLevel <= 0) { this.isInTypeDefinition = false; this.contextPath = []; this.nestingLevel = 0; } } adjustNestingLevel(braceCount) { this.nestingLevel += braceCount; } getCurrentPath() { return [...this.contextPath]; } isParsingType() { return this.isInTypeDefinition; } hasContext() { return this.contextPath.length > 0; } } /** * Identifies different property patterns in TypeScript code */ class PropertyDetector { extractTypeDefinitionName(line) { const typeDefinitionMatch = line.match(/^export type\s+(\w+)\s*=\s*\{?/); return typeDefinitionMatch ? typeDefinitionMatch[1] : null; } extractNestedObjectProperty(line) { const nestedObjectMatch = line.match(/(\w+)\??\s*:\s*\{/); return nestedObjectMatch ? nestedObjectMatch[1] : null; } extractUnionTypeProperty(line) { const unionPropertyMatch = line.match(/(\w+)\??\s*:\s*(.+?)(?:[;,]|$)/); if (unionPropertyMatch) { return { propertyName: unionPropertyMatch[1], typeDefinition: unionPropertyMatch[2].trim(), }; } return null; } hasObjectOpeningBrace(line) { const colonIndex = line.indexOf(":"); if (colonIndex === -1) return false; const afterColon = line.substring(colonIndex + 1); const braceIndex = afterColon.indexOf("{"); const firstQuoteIndex = Math.min(afterColon.indexOf("'") === -1 ? Infinity : afterColon.indexOf("'"), afterColon.indexOf('"') === -1 ? Infinity : afterColon.indexOf('"'), afterColon.indexOf("`") === -1 ? Infinity : afterColon.indexOf("`")); return braceIndex !== -1 && braceIndex < firstQuoteIndex; } } /** * Extracts union type values from type definitions */ class UnionExtractor { extractEnumValues(typeDefinition) { const values = new Set(); const arrayMatch = typeDefinition.match(/Array<([^>]+)>/); if (arrayMatch) { const innerType = arrayMatch[1]; const arrayValues = this.extractEnumValues(innerType); arrayValues.forEach((v) => values.add(v)); return Array.from(values); } if (typeDefinition.includes("|")) { const parts = typeDefinition.split("|").map((part) => part.trim()); for (const part of parts) { const valueMatch = part.match(/^(['"`])(.+?)\1$/); if (valueMatch) { values.add(valueMatch[2]); } } } return Array.from(values); } } /** * Extracts enum information from TypeScript type definitions */ class EnumParser { unionTypeRegex = /(['"`])[^'"`]*\1(\s*\|\s*(['"`])[^'"`]*\3)+/g; typeDefRegex = /^export type\s+(\w+)\s*=\s*(.+);?$/gm; propertyTypeRegex = /(\w+)\??\s*:\s*([^;,}]+)/g; semanticNaming = new semantic_naming_1.SemanticNaming(); // Component instances tokenizer = new TypeScriptTokenizer(); propertyDetector = new PropertyDetector(); unionExtractor = new UnionExtractor(); parseEnumsFromTypeFile(content) { const enums = []; // Parse standalone type definitions enums.push(...this.parseStandaloneTypes(content)); // Parse inline property types within interfaces/types enums.push(...this.extractEnumsFromNestedTypeProperties(content)); return enums; } parseStandaloneTypes(content) { const enums = []; let match = this.typeDefRegex.exec(content); while (match !== null) { const [, typeName, typeDefinition] = match; // Clean up the type definition by removing trailing semicolon const cleanTypeDefinition = typeDefinition.replace(/;$/, "").trim(); const enumValues = this.extractEnumValues(cleanTypeDefinition); if (enumValues.length > 0) { // Use semantic naming for standalone types too const originalTypePath = `export type ${typeName}`; const semanticName = this.semanticNaming.generateName(enumValues, originalTypePath); enums.push({ name: semanticName, values: enumValues, originalTypePath, }); } match = this.typeDefRegex.exec(content); } return enums; } extractEnumsFromNestedTypeProperties(content) { const foundEnums = []; const sourceLines = content.split("\n"); const scopeTracker = new ScopeTracker(); for (const rawLine of sourceLines) { const cleanLine = this.tokenizer.cleanLine(rawLine); if (this.tokenizer.shouldSkipLine(cleanLine)) { continue; } const typeDefinitionName = this.propertyDetector.extractTypeDefinitionName(cleanLine); if (typeDefinitionName) { scopeTracker.startTypeDefinition(typeDefinitionName); if (cleanLine.includes("{")) { scopeTracker.adjustNestingLevel(1); } continue; } if (!scopeTracker.isParsingType()) { continue; } const openingBraceCount = this.tokenizer.countOccurrences(cleanLine, "{"); const closingBraceCount = this.tokenizer.countOccurrences(cleanLine, "}"); const nestedObjectPropertyName = this.propertyDetector.extractNestedObjectProperty(cleanLine); if (nestedObjectPropertyName) { scopeTracker.enterNestedObject(nestedObjectPropertyName, openingBraceCount); continue; } const unionTypeProperty = this.propertyDetector.extractUnionTypeProperty(cleanLine); if (unionTypeProperty && scopeTracker.hasContext()) { const { propertyName, typeDefinition } = unionTypeProperty; const hasObjectBrace = this.propertyDetector.hasObjectOpeningBrace(cleanLine); if (hasObjectBrace) { scopeTracker.enterNestedObject(propertyName, openingBraceCount); continue; } const cleanTypeDefinition = typeDefinition.replace(/[;,]$/, "").trim(); const enumValues = this.unionExtractor.extractEnumValues(cleanTypeDefinition); if (enumValues.length > 0) { const currentPath = scopeTracker.getCurrentPath(); const fullPropertyPath = [...currentPath, propertyName].join("."); const enumName = this.semanticNaming.generateName(enumValues, fullPropertyPath); foundEnums.push({ name: enumName, values: enumValues, originalTypePath: fullPropertyPath, }); } } if (closingBraceCount > 0) { scopeTracker.exitScope(closingBraceCount); } if (openingBraceCount > 0 && !unionTypeProperty && !nestedObjectPropertyName && !typeDefinitionName) { scopeTracker.adjustNestingLevel(openingBraceCount); } } return foundEnums; } extractEnumValues(typeDefinition) { const values = new Set(); // Handle Array<'value1' | 'value2'> patterns first const arrayMatch = typeDefinition.match(/Array<([^>]+)>/); if (arrayMatch) { const innerType = arrayMatch[1]; const arrayValues = this.extractEnumValues(innerType); arrayValues.forEach((v) => values.add(v)); return Array.from(values); } // Handle union types with string literals if (typeDefinition.includes("|")) { const parts = typeDefinition.split("|").map((part) => part.trim()); for (const part of parts) { // Match single quotes, double quotes, or backticks const valueMatch = part.match(/^(['"`])(.+?)\1$/); if (valueMatch) { values.add(valueMatch[2]); } } } return Array.from(values); } capitalizeFirst(str) { return str.charAt(0).toUpperCase() + str.slice(1); } } exports.EnumParser = EnumParser;