Refactor OpenAPI plugin structure by consolidating parser and printer exports, enhancing type safety, and improving error handling in parsing functions. Update tests to reflect new structure and ensure consistent handling of OpenAPI file detection.

This commit is contained in:
Luke Hagar
2025-09-26 21:03:08 +00:00
parent 0901e96671
commit d34e24583b
12 changed files with 796 additions and 754 deletions

View File

@@ -9,6 +9,7 @@
import {
RootKeys,
InfoKeys,
ComponentsKeys,
OperationKeys,
ParameterKeys,
SchemaKeys,
@@ -18,13 +19,21 @@ import {
TagKeys,
ExternalDocsKeys,
WebhookKeys,
OAuthFlowKeys,
PathItemKeys,
RequestBodyKeys,
MediaTypeKeys,
EncodingKeys,
HeaderKeys,
LinkKeys,
ExampleKeys,
DiscriminatorKeys,
XMLKeys,
ContactKeys,
LicenseKeys,
ComponentsKeys,
OAuthFlowKeys,
ServerVariableKeys,
} from '../keys.js';
import { getVendorExtensions, type VendorModule } from './vendor-loader.js';
import { type VendorModule } from './vendor-loader.js';
/**
* Type-safe context-specific extension functions
@@ -528,6 +537,307 @@ export interface VendorExtensions {
* ```
*/
'securityDefinitions'?: ContextExtensionFunction<'securityDefinitions'>;
/** Components section extensions
*
* Available keys:
* - `securitySchemes`
* - `pathItems`
* - `parameters`
* - `headers`
* - `requestBodies`
* - `responses`
* - `callbacks`
* - `links`
* - `schemas`
* - `examples`
*
* @example
* ```typescript
* 'components': (before, after) => {
* return {
* 'x-my-extension': before('schemas'),
* 'x-my-config': after('examples')
* };
* }
* ```
*/
'components'?: ContextExtensionFunction<'components'>;
/** Path item extensions
*
* Available keys:
* - `$ref`
* - `summary`
* - `description`
* - `servers`
* - `parameters`
* - `get`
* - `put`
* - `post`
* - `patch`
* - `delete`
* - `options`
* - `head`
* - `trace`
*
* @example
* ```typescript
* 'pathItem': (before, after) => {
* return {
* 'x-my-extension': before('summary'),
* 'x-my-config': after('parameters')
* };
* }
* ```
*/
'pathItem'?: ContextExtensionFunction<'pathItem'>;
/** Request body extensions
*
* Available keys:
* - `description`
* - `required`
* - `content`
*
* @example
* ```typescript
* 'requestBody': (before, after) => {
* return {
* 'x-my-extension': before('description'),
* 'x-my-config': after('content')
* };
* }
* ```
*/
'requestBody'?: ContextExtensionFunction<'requestBody'>;
/** Media type extensions
*
* Available keys:
* - `schema`
* - `example`
* - `examples`
* - `encoding`
*
* @example
* ```typescript
* 'mediaType': (before, after) => {
* return {
* 'x-my-extension': before('schema'),
* 'x-my-config': after('example')
* };
* }
* ```
*/
'mediaType'?: ContextExtensionFunction<'mediaType'>;
/** Encoding extensions
*
* Available keys:
* - `contentType`
* - `style`
* - `explode`
* - `allowReserved`
* - `headers`
*
* @example
* ```typescript
* 'encoding': (before, after) => {
* return {
* 'x-my-extension': before('contentType'),
* 'x-my-config': after('headers')
* };
* }
* ```
*/
'encoding'?: ContextExtensionFunction<'encoding'>;
/** Header extensions
*
* Available keys:
* - `description`
* - `required`
* - `deprecated`
* - `schema`
* - `content`
* - `type`
* - `format`
* - `style`
* - `explode`
* - `enum`
* - `default`
* - `example`
* - `examples`
* - `items`
* - `collectionFormat`
* - `maxItems`
* - `minItems`
* - `uniqueItems`
* - `minimum`
* - `multipleOf`
* - `exclusiveMinimum`
* - `maximum`
* - `exclusiveMaximum`
* - `pattern`
* - `minLength`
* - `maxLength`
*
* @example
* ```typescript
* 'header': (before, after) => {
* return {
* 'x-my-extension': before('description'),
* 'x-my-config': after('schema')
* };
* }
* ```
*/
'header'?: ContextExtensionFunction<'header'>;
/** Link extensions
*
* Available keys:
* - `operationId`
* - `description`
* - `server`
* - `operationRef`
* - `parameters`
* - `requestBody`
*
* @example
* ```typescript
* 'link': (before, after) => {
* return {
* 'x-my-extension': before('operationId'),
* 'x-my-config': after('parameters')
* };
* }
* ```
*/
'link'?: ContextExtensionFunction<'link'>;
/** Example extensions
*
* Available keys:
* - `summary`
* - `description`
* - `value`
* - `externalValue`
*
* @example
* ```typescript
* 'example': (before, after) => {
* return {
* 'x-my-extension': before('summary'),
* 'x-my-config': after('value')
* };
* }
* ```
*/
'example'?: ContextExtensionFunction<'example'>;
/** Discriminator extensions
*
* Available keys:
* - `propertyName`
* - `mapping`
*
* @example
* ```typescript
* 'discriminator': (before, after) => {
* return {
* 'x-my-extension': before('propertyName'),
* 'x-my-config': after('mapping')
* };
* }
* ```
*/
'discriminator'?: ContextExtensionFunction<'discriminator'>;
/** XML extensions
*
* Available keys:
* - `name`
* - `namespace`
* - `prefix`
* - `attribute`
* - `wrapped`
*
* @example
* ```typescript
* 'xml': (before, after) => {
* return {
* 'x-my-extension': before('name'),
* 'x-my-config': after('namespace')
* };
* }
* ```
*/
'xml'?: ContextExtensionFunction<'xml'>;
/** Contact extensions
*
* Available keys:
* - `name`
* - `email`
* - `url`
*
* @example
* ```typescript
* 'contact': (before, after) => {
* return {
* 'x-my-extension': before('name'),
* 'x-my-config': after('email')
* };
* }
* ```
*/
'contact'?: ContextExtensionFunction<'contact'>;
/** License extensions
*
* Available keys:
* - `name`
* - `identifier`
* - `url`
*
* @example
* ```typescript
* 'license': (before, after) => {
* return {
* 'x-my-extension': before('name'),
* 'x-my-config': after('identifier')
* };
* }
* ```
*/
'license'?: ContextExtensionFunction<'license'>;
/** OAuth flow extensions
*
* Available keys:
* - `authorizationUrl`
* - `tokenUrl`
* - `refreshUrl`
* - `scopes`
*
* @example
* ```typescript
* 'oauthFlow': (before, after) => {
* return {
* 'x-my-extension': before('authorizationUrl'),
* 'x-my-config': after('tokenUrl')
* };
* }
* ```
*/
'oauthFlow'?: ContextExtensionFunction<'oauthFlow'>;
/** Server variable extensions
*
* Available keys:
* - `description`
* - `default`
* - `enum`
*
* @example
* ```typescript
* 'serverVariable': (before, after) => {
* return {
* 'x-my-extension': before('description'),
* 'x-my-config': after('default')
* };
* }
* ```
*/
'serverVariable'?: ContextExtensionFunction<'serverVariable'>;
}
/**
@@ -609,6 +919,7 @@ export function createPositionHelpers<T extends keyof typeof KeyMap>(context: T)
export const KeyMap = {
'top-level': RootKeys,
'info': InfoKeys,
'components': ComponentsKeys,
'operation': OperationKeys,
'parameter': ParameterKeys,
'schema': SchemaKeys,
@@ -618,6 +929,19 @@ export const KeyMap = {
'tag': TagKeys,
'externalDocs': ExternalDocsKeys,
'webhook': WebhookKeys,
'pathItem': PathItemKeys,
'requestBody': RequestBodyKeys,
'mediaType': MediaTypeKeys,
'encoding': EncodingKeys,
'header': HeaderKeys,
'link': LinkKeys,
'example': ExampleKeys,
'discriminator': DiscriminatorKeys,
'xml': XMLKeys,
'contact': ContactKeys,
'license': LicenseKeys,
'oauthFlow': OAuthFlowKeys,
'serverVariable': ServerVariableKeys,
'definitions': SchemaKeys,
'securityDefinitions': SecuritySchemeKeys,
}

View File

@@ -4,7 +4,7 @@
* Loads vendor extensions using static imports for ES module compatibility.
*/
import { before, after, KeyMap, VendorExtensions } from './index.js';
import { before, after, type KeyMap, type VendorExtensions } from './index.js';
// Import vendor extensions statically
import { speakeasy } from './vendor/speakeasy.js';

View File

@@ -1,48 +1,45 @@
import { SupportLanguage, Parser, Printer } from 'prettier';
import type { Plugin } from 'prettier';
import * as yaml from 'js-yaml';
import type{ AstPath, Doc, Parser, ParserOptions, Printer, SupportLanguage } from 'prettier';
import { getVendorExtensions } from './extensions/vendor-loader.js';
export type PrintFn = (path: AstPath) => Doc;
import {
RootKeys,
InfoKeys,
ContactKeys,
LicenseKeys,
ComponentsKeys,
ContactKeys,
DiscriminatorKeys,
EncodingKeys,
ExampleKeys,
ExternalDocsKeys,
HeaderKeys,
InfoKeys,
LicenseKeys,
LinkKeys,
MediaTypeKeys,
OAuthFlowKeys,
OperationKeys,
ParameterKeys,
SchemaKeys,
PathItemKeys,
RequestBodyKeys,
ResponseKeys,
RootKeys,
SchemaKeys,
SecuritySchemeKeys,
OAuthFlowKeys,
ServerKeys,
ServerVariableKeys,
TagKeys,
ExternalDocsKeys,
WebhookKeys,
PathItemKeys,
RequestBodyKeys,
MediaTypeKeys,
EncodingKeys,
HeaderKeys,
LinkKeys,
ExampleKeys,
DiscriminatorKeys,
XMLKeys,
} from './keys.js';
// Type definitions for better type safety
interface OpenAPINode {
type: 'openapi';
isOpenAPI: boolean;
content: any;
originalText: string;
format: 'json' | 'yaml';
}
interface PrettierPath {
getValue(): OpenAPINode;
}
interface OpenAPIPluginOptions {
tabWidth?: number;
printWidth?: number;
@@ -59,18 +56,22 @@ const vendorExtensions = getVendorExtensions();
* Unified parser that can handle both JSON and YAML OpenAPI files
*/
function parseOpenAPIFile(text: string, options?: any): OpenAPINode {
// Try to detect the format based on file extension or content
const filePath = options?.filepath || '';
const isYamlFile = filePath.endsWith('.yaml') || filePath.endsWith('.yml');
const isJsonFile = filePath.endsWith('.json');
console.debug('parseOpenAPIFile', text, options);
// If we can't determine from extension, try to detect from content
let format: 'json' | 'yaml' = 'json'; // default to JSON
if (isYamlFile) {
let format: 'json' | 'yaml' | undefined;
if (options?.filepath) {
switch (true) {
case options?.filepath.endsWith('.yaml') || options?.filepath.endsWith('.yml'):
format = 'yaml';
} else if (isJsonFile) {
break;
case options?.filepath.endsWith('.json'):
format = 'json';
} else {
break;
}
}
if (!format) {
// Try to detect format from content
const trimmedText = text.trim();
if (trimmedText.startsWith('{') || trimmedText.startsWith('[')) {
@@ -80,12 +81,11 @@ function parseOpenAPIFile(text: string, options?: any): OpenAPINode {
}
}
try {
let parsed: any;
if (format === 'json') {
parsed = JSON.parse(text);
} else {
switch (format) {
case 'yaml':
try {
parsed = yaml.load(text, {
schema: yaml.DEFAULT_SCHEMA,
onWarning: (warning) => {
@@ -93,21 +93,48 @@ function parseOpenAPIFile(text: string, options?: any): OpenAPINode {
console.warn('YAML parsing warning:', warning);
}
});
} catch (error) {
throw new Error(`Failed to parse OpenAPI YAML: ${error}`);
}
break;
case 'json':
try {
parsed = JSON.parse(text);
} catch (error) {
throw new Error(`Failed to parse OpenAPI JSON: ${error}`);
}
break;
}
// Check if this is an OpenAPI file
if (!isOpenAPIFile(parsed, filePath)) {
throw new Error('Not an OpenAPI file');
}
let isOpenAPI: boolean;
try {
isOpenAPI = isOpenAPIFile(parsed, options?.filepath);
} catch (error) {
return {
type: 'openapi',
isOpenAPI: false,
content: parsed,
originalText: text,
format: format,
};
} catch (error) {
throw new Error(`Failed to parse OpenAPI ${format.toUpperCase()}: ${error}`);
}
}
switch (isOpenAPI) {
case true:
return {
isOpenAPI: true,
content: parsed,
originalText: text,
format: format,
}
case false:
default:
return {
isOpenAPI: false,
content: parsed,
originalText: text,
format: format,
}
}
}
@@ -229,8 +256,7 @@ function isPathObject(obj: any): boolean {
return Object.keys(obj).some(key => httpMethods.includes(key.toLowerCase()));
}
const plugin: Plugin = {
languages: [
export const languages: Partial<SupportLanguage>[] = [
{
name: 'openapi',
extensions: [
@@ -239,8 +265,9 @@ const plugin: Plugin = {
],
parsers: ['openapi-parser'],
},
],
parsers: {
];
export const parsers: Record<string, Parser> = {
'openapi-parser': {
parse: (text: string, options?: any): OpenAPINode => {
return parseOpenAPIFile(text, options);
@@ -249,16 +276,20 @@ const plugin: Plugin = {
locStart: (node: OpenAPINode) => 0,
locEnd: (node: OpenAPINode) => node.originalText?.length || 0,
},
},
printers: {
}
export const printers: Record<string, Printer> = {
'openapi-ast': {
print: (path: PrettierPath, options?: any, print?: any, ...rest: any[]): string => {
const node = path.getValue();
print: (path: AstPath, options: ParserOptions, print: PrintFn): string => {
const node = path.getNode();
if (!node.isOpenAPI || node.isOpenAPI === false) {
// Return original text unchanged
return options.originalText;
}
return formatOpenAPI(node.content, node.format, options);
},
},
},
};
}
/**
* Unified formatter that outputs in the detected format
@@ -267,10 +298,10 @@ function formatOpenAPI(content: any, format: 'json' | 'yaml', options?: OpenAPIP
// Sort keys for better organization
const sortedContent = sortOpenAPIKeys(content);
if (format === 'json') {
// Format with proper indentation
switch (format) {
case 'json':
return JSON.stringify(sortedContent, null, options?.tabWidth || 2);
} else {
case 'yaml':
// Format YAML with proper indentation and line breaks
return yaml.dump(sortedContent, {
indent: options?.tabWidth || 2,
@@ -282,14 +313,6 @@ function formatOpenAPI(content: any, format: 'json' | 'yaml', options?: OpenAPIP
}
}
function formatOpenAPIJSON(content: any, options?: OpenAPIPluginOptions): string {
return formatOpenAPI(content, 'json', options);
}
function formatOpenAPIYAML(content: any, options?: OpenAPIPluginOptions): string {
return formatOpenAPI(content, 'yaml', options);
}
function sortOpenAPIKeys(obj: any): any {
if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
return obj;
@@ -649,5 +672,3 @@ function getStandardKeysForContext(contextKey: string): readonly string[] {
default: return RootKeys;
}
}
export default plugin;

View File

@@ -1,189 +0,0 @@
import { describe, expect, it } from 'bun:test';
import * as fs from 'fs';
import * as path from 'path';
describe('Build Tests', () => {
describe('Build artifacts', () => {
it('should create dist directory', () => {
const distPath = path.join(process.cwd(), 'dist');
expect(fs.existsSync(distPath)).toBe(true);
});
it('should create main index.js file', () => {
const indexPath = path.join(process.cwd(), 'dist', 'index.js');
expect(fs.existsSync(indexPath)).toBe(true);
});
it('should create TypeScript declaration files', () => {
const dtsPath = path.join(process.cwd(), 'dist', 'index.d.ts');
expect(fs.existsSync(dtsPath)).toBe(true);
});
it('should create source map files', () => {
const mapPath = path.join(process.cwd(), 'dist', 'index.js.map');
expect(fs.existsSync(mapPath)).toBe(true);
});
it('should have valid JavaScript in dist/index.js', () => {
const indexPath = path.join(process.cwd(), 'dist', 'index.js');
const content = fs.readFileSync(indexPath, 'utf-8');
// Should not contain TypeScript syntax
expect(content).not.toContain(': string');
expect(content).not.toContain(': number');
expect(content).not.toContain('interface ');
// Note: 'type ' might appear in comments or strings, so we check for type annotations instead
expect(content).not.toMatch(/\btype\s+[A-Z]/);
// Should be valid JavaScript (but may contain ES module syntax)
// We can't use new Function() with ES modules, so we just check it's not empty
expect(content.length).toBeGreaterThan(0);
});
it('should export the plugin as default export', () => {
const indexPath = path.join(process.cwd(), 'dist', 'index.js');
const content = fs.readFileSync(indexPath, 'utf-8');
// Should have module.exports (CommonJS format)
expect(content).toContain('module.exports');
});
it('should have proper module structure', () => {
const indexPath = path.join(process.cwd(), 'dist', 'index.js');
const content = fs.readFileSync(indexPath, 'utf-8');
// Should be CommonJS module
expect(content).toContain('require');
expect(content).toContain('module.exports');
});
});
describe('Package.json validation', () => {
it('should have correct main field', () => {
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
expect(packageJson.main).toBe('dist/index.js');
});
it('should have correct types field', () => {
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
expect(packageJson.types).toBe('./dist/index.d.ts');
});
it('should have correct exports field', () => {
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
expect(packageJson.exports).toBeDefined();
expect(packageJson.exports['.']).toBeDefined();
expect(packageJson.exports['.'].default).toBe('./dist/index.js');
});
it('should include required files in files array', () => {
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
expect(packageJson.files).toContain('dist/index.js');
expect(packageJson.files).toContain('dist/index.d.ts');
});
it('should have required metadata', () => {
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
expect(packageJson.name).toBeDefined();
expect(packageJson.version).toBeDefined();
expect(packageJson.description).toBeDefined();
expect(packageJson.author).toBeDefined();
expect(packageJson.license).toBeDefined();
expect(packageJson.keywords).toBeDefined();
expect(packageJson.repository).toBeDefined();
expect(packageJson.bugs).toBeDefined();
expect(packageJson.homepage).toBeDefined();
});
it('should have correct peer dependencies', () => {
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
expect(packageJson.peerDependencies).toBeDefined();
expect(packageJson.peerDependencies.prettier).toBeDefined();
});
it('should have correct engines requirement', () => {
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
expect(packageJson.engines).toBeDefined();
expect(packageJson.engines.node).toBe('>=18.0.0');
});
});
describe('NPM package validation', () => {
it('should have all required files for npm publish', () => {
const requiredFiles = [
'dist/index.js',
'dist/index.d.ts',
'dist/index.js.map',
'README.md',
'package.json'
];
requiredFiles.forEach(file => {
const filePath = path.join(process.cwd(), file);
expect(fs.existsSync(filePath)).toBe(true);
});
});
it('should not include development files in npm package', () => {
const excludedFiles = [
'src/',
'test/',
'.github/',
'.husky/',
'.eslintrc.js',
'.prettierrc.js',
'tsconfig.json',
'bunfig.toml'
];
// These files should not be in the npm package
// (This is handled by .npmignore, but we can verify the ignore file exists)
expect(fs.existsSync('.npmignore')).toBe(true);
});
it('should have valid package.json for npm', () => {
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
// Required fields for npm
expect(packageJson.name).toBeTruthy();
expect(packageJson.version).toBeTruthy();
expect(packageJson.main).toBeTruthy();
expect(packageJson.license).toBeTruthy();
// Should not have private field (or it should be false)
if (packageJson.private !== undefined) {
expect(packageJson.private).toBe(false);
}
});
});
describe('TypeScript compilation', () => {
it('should compile without errors', () => {
// This test assumes the build has already been run
// In a real scenario, you might want to run tsc programmatically
const distPath = path.join(process.cwd(), 'dist');
expect(fs.existsSync(distPath)).toBe(true);
});
it('should generate declaration files', () => {
const dtsPath = path.join(process.cwd(), 'dist', 'index.d.ts');
const content = fs.readFileSync(dtsPath, 'utf-8');
// Should contain type declarations
expect(content).toContain('declare');
expect(content).toContain('export');
});
it('should generate source maps', () => {
const mapPath = path.join(process.cwd(), 'dist', 'index.js.map');
const content = fs.readFileSync(mapPath, 'utf-8');
const sourceMap = JSON.parse(content);
// Should be valid source map
expect(sourceMap.version).toBeDefined();
expect(sourceMap.sources).toBeDefined();
expect(sourceMap.mappings).toBeDefined();
});
});
});

View File

@@ -1,79 +1,10 @@
import { describe, expect, it } from 'bun:test';
import plugin from '../src/index';
import { parsers, printers } from '../src/index';
describe('Coverage Tests', () => {
describe('Error handling and edge cases', () => {
it('should handle null and undefined content', () => {
const parser = plugin.parsers?.['openapi-parser'];
expect(parser).toBeDefined();
// Test with null content
expect(() => {
// @ts-expect-error We are testing edge cases
parser?.parse('null', { filepath: 'test.json' });
}).toThrow();
// Test with undefined content
expect(() => {
// @ts-expect-error We are testing edge cases
parser?.parse('undefined', { filepath: 'test.json' });
}).toThrow();
});
it('should handle non-object content', () => {
const parser = plugin.parsers?.['openapi-parser'];
expect(parser).toBeDefined();
// Test with string content
expect(() => {
// @ts-expect-error We are testing edge cases
parser?.parse('"string"', { filepath: 'test.json' });
}).toThrow();
// Test with number content
expect(() => {
// @ts-expect-error We are testing edge cases
parser?.parse('123', { filepath: 'test.json' });
}).toThrow();
});
it('should handle array content', () => {
const parser = plugin.parsers?.['openapi-parser'];
expect(parser).toBeDefined();
// Test with array content
expect(() => {
// @ts-expect-error We are testing edge cases
parser?.parse('[]', { filepath: 'test.json' });
}).toThrow();
});
it('should handle malformed JSON', () => {
const parser = plugin.parsers?.['openapi-parser'];
expect(parser).toBeDefined();
// Test with malformed JSON
expect(() => {
// @ts-expect-error We are testing edge cases
parser?.parse('{invalid json}', { filepath: 'test.json' });
}).toThrow('Failed to parse OpenAPI JSON');
});
it('should handle malformed YAML', () => {
const parser = plugin.parsers?.['openapi-parser'];
expect(parser).toBeDefined();
// Test with malformed YAML
expect(() => {
// @ts-expect-error We are testing edge cases
parser?.parse('invalid: yaml: content:', { filepath: 'test.yaml' });
}).toThrow('Failed to parse OpenAPI YAML');
});
});
describe('File path detection', () => {
it('should detect OpenAPI files in component directories', () => {
const parser = plugin.parsers?.['openapi-parser'];
const parser = parsers?.['openapi-parser'];
expect(parser).toBeDefined();
const testYaml = `type: object
@@ -100,13 +31,13 @@ properties:
// @ts-expect-error We are testing edge cases
const result = parser?.parse(testYaml, { filepath: path });
expect(result).toBeDefined();
expect(result?.type).toBe('openapi');
expect(result?.isOpenAPI).toBeTrue();
expect(result?.format).toBe('yaml');
});
});
it('should handle files without filepath', () => {
const parser = plugin.parsers?.['openapi-parser'];
const parser = parsers?.['openapi-parser'];
expect(parser).toBeDefined();
const testYaml = `openapi: 3.0.0
@@ -117,13 +48,13 @@ info:
// @ts-expect-error We are testing edge cases
const result = parser?.parse(testYaml, { filepath: 'test.yaml' });
expect(result).toBeDefined();
expect(result?.type).toBe('openapi');
expect(result?.isOpenAPI).toBeTrue();
});
});
describe('Object type detection', () => {
it('should detect operation objects', () => {
const parser = plugin.parsers?.['openapi-parser'];
const parser = parsers?.['openapi-parser'];
expect(parser).toBeDefined();
const operationYaml = `get:
@@ -135,11 +66,11 @@ info:
// @ts-expect-error We are testing edge cases
const result = parser?.parse(operationYaml, { filepath: 'paths/users.yaml' });
expect(result).toBeDefined();
expect(result?.type).toBe('openapi');
expect(result?.isOpenAPI).toBeTrue();
});
it('should detect parameter objects', () => {
const parser = plugin.parsers?.['openapi-parser'];
const parser = parsers?.['openapi-parser'];
expect(parser).toBeDefined();
const parameterYaml = `name: id
@@ -151,11 +82,11 @@ schema:
// @ts-expect-error We are testing edge cases
const result = parser?.parse(parameterYaml, { filepath: 'components/parameters/UserId.yaml' });
expect(result).toBeDefined();
expect(result?.type).toBe('openapi');
expect(result?.isOpenAPI).toBeTrue();
});
it('should detect schema objects', () => {
const parser = plugin.parsers?.['openapi-parser'];
const parser = parsers?.['openapi-parser'];
expect(parser).toBeDefined();
const schemaYaml = `type: object
@@ -170,11 +101,11 @@ required:
// @ts-expect-error We are testing edge cases
const result = parser?.parse(schemaYaml, { filepath: 'components/schemas/User.yaml' });
expect(result).toBeDefined();
expect(result?.type).toBe('openapi');
expect(result?.isOpenAPI).toBeTrue();
});
it('should detect response objects', () => {
const parser = plugin.parsers?.['openapi-parser'];
const parser = parsers?.['openapi-parser'];
expect(parser).toBeDefined();
const responseYaml = `description: User response
@@ -186,11 +117,11 @@ content:
// @ts-expect-error We are testing edge cases
const result = parser?.parse(responseYaml, { filepath: 'components/responses/UserResponse.yaml' });
expect(result).toBeDefined();
expect(result?.type).toBe('openapi');
expect(result?.isOpenAPI).toBeTrue();
});
it('should detect security scheme objects', () => {
const parser = plugin.parsers?.['openapi-parser'];
const parser = parsers?.['openapi-parser'];
expect(parser).toBeDefined();
const securityYaml = `type: http
@@ -200,11 +131,11 @@ bearerFormat: JWT`;
// @ts-expect-error We are testing edge cases
const result = parser?.parse(securityYaml, { filepath: 'components/securitySchemes/BearerAuth.yaml' });
expect(result).toBeDefined();
expect(result?.type).toBe('openapi');
expect(result?.isOpenAPI).toBeTrue();
});
it('should detect server objects', () => {
const parser = plugin.parsers?.['openapi-parser'];
const parser = parsers?.['openapi-parser'];
expect(parser).toBeDefined();
const serverYaml = `url: https://api.example.com
@@ -213,11 +144,11 @@ description: Production server`;
// @ts-expect-error We are testing edge cases
const result = parser?.parse(serverYaml, { filepath: 'servers/production.yaml' });
expect(result).toBeDefined();
expect(result?.type).toBe('openapi');
expect(result?.isOpenAPI).toBeTrue();
});
it('should detect tag objects', () => {
const parser = plugin.parsers?.['openapi-parser'];
const parser = parsers?.['openapi-parser'];
expect(parser).toBeDefined();
const tagYaml = `name: users
@@ -226,11 +157,11 @@ description: User management operations`;
// @ts-expect-error We are testing edge cases
const result = parser?.parse(tagYaml, { filepath: 'tags/users.yaml' });
expect(result).toBeDefined();
expect(result?.type).toBe('openapi');
expect(result?.isOpenAPI).toBeTrue();
});
it('should detect external docs objects', () => {
const parser = plugin.parsers?.['openapi-parser'];
const parser = parsers?.['openapi-parser'];
expect(parser).toBeDefined();
const externalDocsYaml = `url: https://example.com/docs
@@ -239,11 +170,11 @@ description: External documentation`;
// @ts-expect-error We are testing edge cases
const result = parser?.parse(externalDocsYaml, { filepath: 'externalDocs/api.yaml' });
expect(result).toBeDefined();
expect(result?.type).toBe('openapi');
expect(result?.isOpenAPI).toBeTrue();
});
it('should detect webhook objects', () => {
const parser = plugin.parsers?.['openapi-parser'];
const parser = parsers?.['openapi-parser'];
expect(parser).toBeDefined();
const webhookYaml = `post:
@@ -255,17 +186,17 @@ description: External documentation`;
// @ts-expect-error We are testing edge cases
const result = parser?.parse(webhookYaml, { filepath: 'webhooks/messageCreated.yaml' });
expect(result).toBeDefined();
expect(result?.type).toBe('openapi');
expect(result?.isOpenAPI).toBeTrue();
});
});
describe('Sorting functions', () => {
it('should handle path sorting by specificity', () => {
const printer = plugin.printers?.['openapi-ast'];
const printer = printers?.['openapi-ast'];
expect(printer).toBeDefined();
const testData = {
type: 'openapi',
isOpenAPI: true,
content: {
openapi: '3.0.0',
info: { title: 'Test API', version: '1.0.0' },
@@ -280,7 +211,7 @@ description: External documentation`;
};
// @ts-expect-error We are testing edge cases
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => '');
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => '');
expect(result).toBeDefined();
if (result && typeof result === 'string') {
@@ -294,11 +225,11 @@ description: External documentation`;
});
it('should handle response code sorting', () => {
const printer = plugin.printers?.['openapi-ast'];
const printer = printers?.['openapi-ast'];
expect(printer).toBeDefined();
const testData = {
type: 'openapi',
isOpenAPI: true,
content: {
openapi: '3.0.0',
info: { title: 'Test API', version: '1.0.0' },
@@ -319,7 +250,7 @@ description: External documentation`;
};
// @ts-expect-error We are testing edge cases
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => '');
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => '');
expect(result).toBeDefined();
if (result && typeof result === 'string') {
@@ -335,11 +266,11 @@ description: External documentation`;
describe('Context key detection', () => {
it('should handle nested path contexts', () => {
const printer = plugin.printers?.['openapi-ast'];
const printer = printers?.['openapi-ast'];
expect(printer).toBeDefined();
const testData = {
type: 'openapi',
isOpenAPI: true,
content: {
openapi: '3.0.0',
info: { title: 'Test API', version: '1.0.0' },
@@ -360,7 +291,7 @@ description: External documentation`;
};
// @ts-expect-error We are testing edge cases
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => '');
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => '');
expect(result).toBeDefined();
if (result && typeof result === 'string') {

View File

@@ -1,9 +1,9 @@
import { describe, expect, it } from 'bun:test';
import plugin from '../src/index';
import {parsers, printers} from '../src/index';
describe('Custom Extensions Support', () => {
it('should handle custom extensions in top-level keys', () => {
const parser = plugin.parsers?.['openapi-parser'];
const parser = parsers?.['openapi-parser'];
expect(parser).toBeDefined();
const testJson = {
@@ -21,7 +21,7 @@ describe('Custom Extensions Support', () => {
});
it('should handle custom extensions in info section', () => {
const parser = plugin.parsers?.['openapi-parser'];
const parser = parsers?.['openapi-parser'];
expect(parser).toBeDefined();
const testJson = {
@@ -43,7 +43,7 @@ describe('Custom Extensions Support', () => {
});
it('should handle custom extensions in operation objects', () => {
const parser = plugin.parsers?.['openapi-parser'];
const parser = parsers?.['openapi-parser'];
expect(parser).toBeDefined();
const testJson = {
@@ -68,7 +68,7 @@ describe('Custom Extensions Support', () => {
});
it('should handle custom extensions in schema objects', () => {
const parser = plugin.parsers?.['openapi-parser'];
const parser = parsers?.['openapi-parser'];
expect(parser).toBeDefined();
const testJson = {
@@ -95,10 +95,12 @@ describe('Custom Extensions Support', () => {
});
it('should format JSON with custom extensions', () => {
const printer = plugin.printers?.['openapi-ast'];
const printer = printers?.['openapi-ast'];
expect(printer).toBeDefined();
const testData = {
isOpenAPI: true,
format: 'json',
content: {
'x-custom-field': 'value',
'openapi': '3.0.0',
@@ -109,17 +111,19 @@ describe('Custom Extensions Support', () => {
};
// @ts-expect-error We are mocking things here so we don't need to pass a print function
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => '');
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => '');
expect(result).toBeDefined();
expect(result).toContain('x-custom-field');
expect(result).toContain('openapi');
});
it('should format YAML with custom extensions', () => {
const printer = plugin.printers?.['openapi-ast'];
const printer = printers?.['openapi-ast'];
expect(printer).toBeDefined();
const testData = {
isOpenAPI: true,
format: 'yaml',
content: {
'x-custom-field': 'value',
'openapi': '3.0.0',
@@ -130,14 +134,14 @@ describe('Custom Extensions Support', () => {
};
// @ts-expect-error We are mocking things here so we don't need to pass a print function
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => '');
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => '');
expect(result).toBeDefined();
expect(result).toContain('x-custom-field:');
expect(result).toContain('openapi:');
});
it('should handle unknown keys alphabetically at the end', () => {
const parser = plugin.parsers?.['openapi-parser'];
const parser = parsers?.['openapi-parser'];
expect(parser).toBeDefined();
const testJson = {
@@ -156,10 +160,12 @@ describe('Custom Extensions Support', () => {
describe('Custom extension positioning', () => {
it('should position custom extensions correctly in top-level', () => {
const printer = plugin.printers?.['openapi-ast'];
const printer = printers?.['openapi-ast'];
expect(printer).toBeDefined();
const testData = {
isOpenAPI: true,
format: 'json',
content: {
'x-custom-field': 'value',
'openapi': '3.0.0',
@@ -170,7 +176,7 @@ describe('Custom Extensions Support', () => {
};
// @ts-expect-error We are mocking things here
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => '');
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => '');
expect(result).toBeDefined();
if (!result) {
@@ -193,10 +199,12 @@ describe('Custom Extensions Support', () => {
});
it('should position custom extensions correctly in info section', () => {
const printer = plugin.printers?.['openapi-ast'];
const printer = printers?.['openapi-ast'];
expect(printer).toBeDefined();
const testData = {
isOpenAPI: true,
format: 'yaml',
content: {
'openapi': '3.0.0',
'info': {
@@ -210,7 +218,7 @@ describe('Custom Extensions Support', () => {
};
// @ts-expect-error We are mocking things here
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => '');
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => '');
expect(result).toBeDefined();
if (!result) {
@@ -233,10 +241,12 @@ describe('Custom Extensions Support', () => {
});
it('should position custom extensions correctly in operation objects', () => {
const printer = plugin.printers?.['openapi-ast'];
const printer = printers?.['openapi-ast'];
expect(printer).toBeDefined();
const testData = {
isOpenAPI: true,
format: 'yaml',
content: {
'openapi': '3.0.0',
'info': { 'title': 'Test API', 'version': '1.0.0' },
@@ -254,7 +264,7 @@ describe('Custom Extensions Support', () => {
};
// @ts-expect-error We are mocking things here
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => '');
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => '');
expect(result).toBeDefined();
if (!result) {
@@ -274,10 +284,12 @@ describe('Custom Extensions Support', () => {
});
it('should position custom extensions correctly in schema objects', () => {
const printer = plugin.printers?.['openapi-ast'];
const printer = printers?.['openapi-ast'];
expect(printer).toBeDefined();
const testData = {
isOpenAPI: true,
format: 'yaml',
content: {
'openapi': '3.0.0',
'info': { 'title': 'Test API', 'version': '1.0.0' },
@@ -295,7 +307,7 @@ describe('Custom Extensions Support', () => {
};
// @ts-expect-error We are mocking things here
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => '');
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => '');
expect(result).toBeDefined();
if (!result) {
@@ -318,10 +330,12 @@ describe('Custom Extensions Support', () => {
describe('Unknown key handling', () => {
it('should sort unknown keys alphabetically at the end', () => {
const printer = plugin.printers?.['openapi-ast'];
const printer = printers?.['openapi-ast'];
expect(printer).toBeDefined();
const testData = {
isOpenAPI: true,
format: 'json',
content: {
'openapi': '3.0.0',
'info': { 'title': 'Test API', 'version': '1.0.0' },
@@ -332,7 +346,7 @@ describe('Custom Extensions Support', () => {
};
// @ts-expect-error We are mocking things here
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => '');
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => '');
expect(result).toBeDefined();
if (!result) {
@@ -358,10 +372,12 @@ describe('Custom Extensions Support', () => {
});
it('should handle mixed custom extensions and unknown keys', () => {
const printer = plugin.printers?.['openapi-ast'];
const printer = printers?.['openapi-ast'];
expect(printer).toBeDefined();
const testData = {
isOpenAPI: true,
format: 'json',
content: {
'openapi': '3.0.0',
'info': { 'title': 'Test API', 'version': '1.0.0' },
@@ -374,7 +390,7 @@ describe('Custom Extensions Support', () => {
};
// @ts-expect-error We are mocking things here
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => '');
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => '');
expect(result).toBeDefined();
if (!result) {

View File

@@ -1,9 +1,9 @@
import { describe, expect, it } from 'bun:test';
import plugin from '../src/index';
import { parsers } from '../src/index';
describe('File Detection Tests', () => {
it('should detect OpenAPI root files', () => {
const parser = plugin.parsers?.['openapi-parser'];
const parser = parsers?.['openapi-parser'];
expect(parser).toBeDefined();
const testYaml = `openapi: 3.0.0
@@ -21,13 +21,13 @@ paths:
const result = parser?.parse(testYaml, { filepath: 'openapi.yaml' });
expect(result).toBeDefined();
expect(result?.type).toBe('openapi');
expect(result?.isOpenAPI).toBeTrue();
expect(result?.format).toBe('yaml');
expect(result?.content.openapi).toBe('3.0.0');
});
it('should detect partial schema files', () => {
const parser = plugin.parsers?.['openapi-parser'];
const parser = parsers?.['openapi-parser'];
expect(parser).toBeDefined();
const schemaYaml = `type: object
@@ -44,13 +44,13 @@ required:
const result = parser?.parse(schemaYaml, { filepath: 'components/schemas/User.yaml' });
expect(result).toBeDefined();
expect(result?.type).toBe('openapi');
expect(result?.isOpenAPI).toBeTrue();
expect(result?.format).toBe('yaml');
expect(result?.content.type).toBe('object');
});
it('should detect parameter files', () => {
const parser = plugin.parsers?.['openapi-parser'];
const parser = parsers?.['openapi-parser'];
expect(parser).toBeDefined();
const parameterYaml = `name: id
@@ -64,13 +64,13 @@ schema:
const result = parser?.parse(parameterYaml, { filepath: 'components/parameters/UserId.yaml' });
expect(result).toBeDefined();
expect(result?.type).toBe('openapi');
expect(result?.isOpenAPI).toBeTrue();
expect(result?.format).toBe('yaml');
expect(result?.content.name).toBe('id');
});
it('should detect response files', () => {
const parser = plugin.parsers?.['openapi-parser'];
const parser = parsers?.['openapi-parser'];
expect(parser).toBeDefined();
const responseYaml = `description: User response
@@ -88,13 +88,13 @@ content:
const result = parser?.parse(responseYaml, { filepath: 'components/responses/UserResponse.yaml' });
expect(result).toBeDefined();
expect(result?.type).toBe('openapi');
expect(result?.isOpenAPI).toBeTrue();
expect(result?.format).toBe('yaml');
expect(result?.content.description).toBe('User response');
});
it('should detect path files', () => {
const parser = plugin.parsers?.['openapi-parser'];
const parser = parsers?.['openapi-parser'];
expect(parser).toBeDefined();
const pathYaml = `get:
@@ -114,14 +114,14 @@ post:
const result = parser?.parse(pathYaml, { filepath: 'paths/users.yaml' });
expect(result).toBeDefined();
expect(result?.type).toBe('openapi');
expect(result?.isOpenAPI).toBeTrue();
expect(result?.format).toBe('yaml');
expect(result?.content.get).toBeDefined();
expect(result?.content.post).toBeDefined();
});
it('should detect security scheme files', () => {
const parser = plugin.parsers?.['openapi-parser'];
const parser = parsers?.['openapi-parser'];
expect(parser).toBeDefined();
const securityYaml = `type: http
@@ -133,25 +133,34 @@ description: JWT authentication`;
const result = parser?.parse(securityYaml, { filepath: 'components/securitySchemes/BearerAuth.yaml' });
expect(result).toBeDefined();
expect(result?.type).toBe('openapi');
expect(result?.isOpenAPI).toBeTrue();
expect(result?.format).toBe('yaml');
expect(result?.content.type).toBe('http');
});
it('should reject non-OpenAPI files', () => {
const parser = plugin.parsers?.['openapi-parser'];
it('should handle non-OpenAPI files', () => {
const parser = parsers?.['openapi-parser'];
expect(parser).toBeDefined();
const nonOpenAPIYaml = `name: John
age: 30
city: New York`;
const parsedYaml = {
name: 'John',
age: 30,
city: 'New York'
}
// @ts-expect-error We are mocking things here
expect(() => parser?.parse(nonOpenAPIYaml, { filepath: 'config/data.yaml' })).toThrow('Not an OpenAPI file');
const parsedData = parser?.parse(nonOpenAPIYaml, { filepath: 'config/data.yaml' })
expect(parsedData).toBeDefined()
expect(parsedData?.isOpenAPI).toBeFalse();
});
it('should accept files in OpenAPI directories even with simple content', () => {
const parser = plugin.parsers?.['openapi-parser'];
const parser = parsers?.['openapi-parser'];
expect(parser).toBeDefined();
const simpleYaml = `name: John
@@ -161,12 +170,12 @@ city: New York`;
// @ts-expect-error We are mocking things here
const result = parser?.parse(simpleYaml, { filepath: 'components/schemas/User.yaml' });
expect(result).toBeDefined();
expect(result?.type).toBe('openapi');
expect(result?.isOpenAPI).toBeTrue();
expect(result?.format).toBe('yaml');
});
it('should support component directory patterns', () => {
const parser = plugin.parsers?.['openapi-parser'];
const parser = parsers?.['openapi-parser'];
expect(parser).toBeDefined();
const componentYaml = `type: object
@@ -193,7 +202,7 @@ properties:
// @ts-expect-error We are mocking things here
const result = parser?.parse(componentYaml, { filepath: path });
expect(result).toBeDefined();
expect(result?.type).toBe('openapi');
expect(result?.isOpenAPI).toBeTrue();
expect(result?.format).toBe('yaml');
});
});

View File

@@ -1,5 +1,5 @@
import { describe, expect, it } from 'bun:test';
import plugin from '../src/index';
import {parsers, printers} from '../src/index';
describe('Integration Tests', () => {
describe('Real OpenAPI file processing', () => {
@@ -213,18 +213,18 @@ describe('Integration Tests', () => {
]
};
const printer = plugin.printers?.['openapi-ast'];
const printer = printers?.['openapi-ast'];
expect(printer).toBeDefined();
const testData = {
type: 'openapi',
isOpenAPI: true,
content: openApiContent,
originalText: '',
format: 'json'
};
// @ts-expect-error We are mocking things here
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => '');
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => '');
expect(result).toBeDefined();
if (result && typeof result === 'string') {
@@ -365,18 +365,18 @@ describe('Integration Tests', () => {
]
};
const printer = plugin.printers?.['openapi-ast'];
const printer = printers?.['openapi-ast'];
expect(printer).toBeDefined();
const testData = {
type: 'openapi',
isOpenAPI: true,
content: swaggerContent,
originalText: '',
format: 'json'
};
// @ts-expect-error We are mocking things here
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => '');
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => '');
expect(result).toBeDefined();
if (result && typeof result === 'string') {
@@ -419,18 +419,18 @@ describe('Integration Tests', () => {
}
};
const printer = plugin.printers?.['openapi-ast'];
const printer = printers?.['openapi-ast'];
expect(printer).toBeDefined();
const testData = {
type: 'openapi',
isOpenAPI: true,
content: yamlContent,
originalText: '',
format: 'yaml'
};
// @ts-expect-error We are mocking things here
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => '');
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => '');
expect(result).toBeDefined();
if (result) {
@@ -451,7 +451,7 @@ describe('Integration Tests', () => {
describe('Error handling', () => {
it('should handle malformed JSON gracefully', () => {
const parser = plugin.parsers?.['openapi-parser'];
const parser = parsers?.['openapi-parser'];
expect(parser).toBeDefined();
const malformedJson = '{"openapi": "3.0.0", "info": {';
@@ -461,7 +461,7 @@ describe('Integration Tests', () => {
});
it('should handle malformed YAML gracefully', () => {
const parser = plugin.parsers?.['openapi-parser'];
const parser = parsers?.['openapi-parser'];
expect(parser).toBeDefined();
const malformedYaml = 'openapi: 3.0.0\ninfo:\n title: Test\n version: 1.0.0\n invalid: [';
@@ -470,14 +470,23 @@ describe('Integration Tests', () => {
expect(() => parser?.parse(malformedYaml, { filepath: 'test.yaml' })).toThrow();
});
it('should reject non-OpenAPI content', () => {
const parser = plugin.parsers?.['openapi-parser'];
it('should handle non-OpenAPI content', () => {
const parser = parsers?.['openapi-parser'];
expect(parser).toBeDefined();
const nonOpenAPI = '{"name": "John", "age": 30}';
const parsedJSON = {
name: 'John',
age: 30
}
// @ts-expect-error We are mocking things here
expect(() => parser?.parse(nonOpenAPI, { filepath: 'test.json' })).toThrow('Not an OpenAPI file');
const parsedData = parser?.parse(nonOpenAPI, { filepath: 'test.json' })
expect(parsedData).toBeDefined();
expect(parsedData?.isOpenAPI).toBeFalse();
expect(parsedData?.content).toBeDefined();
expect(parsedData?.content).toEqual(parsedJSON);
});
});
@@ -506,11 +515,11 @@ describe('Integration Tests', () => {
};
}
const printer = plugin.printers?.['openapi-ast'];
const printer = printers?.['openapi-ast'];
expect(printer).toBeDefined();
const testData = {
type: 'openapi',
isOpenAPI: true,
content: largeOpenAPI,
originalText: '',
format: 'json'
@@ -519,7 +528,7 @@ describe('Integration Tests', () => {
const startTime = Date.now();
// @ts-expect-error We are mocking things here
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => '');
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => '');
const endTime = Date.now();
const duration = endTime - startTime;

View File

@@ -1,14 +1,15 @@
import { describe, it, expect } from 'bun:test';
import plugin from '../src/index';
import { printers } from '../src/index';
describe('Key Ordering Tests', () => {
describe('Info section key ordering', () => {
it('should sort info keys correctly', () => {
const printer = plugin.printers?.['openapi-ast'];
const printer = printers?.['openapi-ast'];
expect(printer).toBeDefined();
const testData = {
type: 'openapi',
isOpenAPI: true,
format: 'json',
content: {
openapi: '3.0.0',
info: {
@@ -21,10 +22,9 @@ describe('Key Ordering Tests', () => {
}
},
originalText: '',
format: 'json'
};
// @ts-expect-error We are mocking things here so we don't need to pass a print function
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 });
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
expect(result).toBeDefined();
if (!result) {
@@ -51,11 +51,12 @@ describe('Key Ordering Tests', () => {
describe('Operation key ordering', () => {
it('should sort operation keys correctly', () => {
const printer = plugin.printers?.['openapi-ast'];
const printer = printers?.['openapi-ast'];
expect(printer).toBeDefined();
const testData = {
type: 'openapi',
isOpenAPI: true,
format: 'yaml',
content: {
openapi: '3.0.0',
info: { title: 'Test API', version: '1.0.0' },
@@ -77,12 +78,11 @@ describe('Key Ordering Tests', () => {
}
}
},
originalText: '',
format: 'json'
originalText: ''
};
// @ts-expect-error We are mocking things here so we don't need to pass a print function
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 });
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
expect(result).toBeDefined();
if (!result) {
@@ -119,10 +119,12 @@ describe('Key Ordering Tests', () => {
describe('Schema key ordering', () => {
it('should sort schema keys correctly', () => {
const printer = plugin.printers?.['openapi-ast'];
const printer = printers?.['openapi-ast'];
expect(printer).toBeDefined();
const testData = {
isOpenAPI: true,
format: 'yaml',
content: {
openapi: '3.0.0',
info: { title: 'Test API', version: '1.0.0' },
@@ -169,7 +171,8 @@ describe('Key Ordering Tests', () => {
};
// @ts-expect-error We are mocking things here so we don't need to pass a print function
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 });
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
console.log('result', result);
expect(result).toBeDefined();
if (!result) {
@@ -247,10 +250,12 @@ describe('Key Ordering Tests', () => {
describe('Response key ordering', () => {
it('should sort response keys correctly', () => {
const printer = plugin.printers?.['openapi-ast'];
const printer = printers?.['openapi-ast'];
expect(printer).toBeDefined();
const testData = {
isOpenAPI: true,
format: 'json',
content: {
openapi: '3.0.0',
info: { title: 'Test API', version: '1.0.0' },
@@ -271,7 +276,7 @@ describe('Key Ordering Tests', () => {
}
};
// @ts-expect-error We are mocking things here so we don't need to pass a print function
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 });
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
expect(result).toBeDefined();
if (!result) {
@@ -294,10 +299,12 @@ describe('Key Ordering Tests', () => {
describe('Parameter key ordering', () => {
it('should sort parameter keys correctly', () => {
const printer = plugin.printers?.['openapi-ast'];
const printer = printers?.['openapi-ast'];
expect(printer).toBeDefined();
const testData = {
isOpenAPI: true,
format: 'yaml',
content: {
openapi: '3.0.0',
info: { title: 'Test API', version: '1.0.0' },
@@ -327,7 +334,7 @@ describe('Key Ordering Tests', () => {
}
};
// @ts-expect-error We are mocking things here so we don't need to pass a print function
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 });
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
expect(result).toBeDefined();
if (!result) {
@@ -366,10 +373,12 @@ describe('Key Ordering Tests', () => {
describe('Security scheme key ordering', () => {
it('should sort security scheme keys correctly', () => {
const printer = plugin.printers?.['openapi-ast'];
const printer = printers?.['openapi-ast'];
expect(printer).toBeDefined();
const testData = {
isOpenAPI: true,
format: 'yaml',
content: {
openapi: '3.0.0',
info: { title: 'Test API', version: '1.0.0' },
@@ -395,7 +404,7 @@ describe('Key Ordering Tests', () => {
}
};
// @ts-expect-error We are mocking things here so we don't need to pass a print function
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 });
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
expect(result).toBeDefined();
if (!result) {
@@ -426,10 +435,12 @@ describe('Key Ordering Tests', () => {
describe('Server key ordering', () => {
it('should sort server keys correctly', () => {
const printer = plugin.printers?.['openapi-ast'];
const printer = printers?.['openapi-ast'];
expect(printer).toBeDefined();
const testData = {
isOpenAPI: true,
format: 'json',
content: {
openapi: '3.0.0',
info: { title: 'Test API', version: '1.0.0' },
@@ -443,7 +454,7 @@ describe('Key Ordering Tests', () => {
}
};
// @ts-expect-error We are mocking things here so we don't need to pass a print function
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 });
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
expect(result).toBeDefined();
if (!result) {
@@ -465,10 +476,12 @@ describe('Key Ordering Tests', () => {
describe('Tag key ordering', () => {
it('should sort tag keys correctly', () => {
const printer = plugin.printers?.['openapi-ast'];
const printer = printers?.['openapi-ast'];
expect(printer).toBeDefined();
const testData = {
isOpenAPI: true,
format: 'json',
content: {
openapi: '3.0.0',
info: { title: 'Test API', version: '1.0.0' },
@@ -482,7 +495,7 @@ describe('Key Ordering Tests', () => {
}
};
// @ts-expect-error We are mocking things here so we don't need to pass a print function
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 });
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
expect(result).toBeDefined();
if (!result) {
@@ -502,10 +515,12 @@ describe('Key Ordering Tests', () => {
describe('External docs key ordering', () => {
it('should sort external docs keys correctly', () => {
const printer = plugin.printers?.['openapi-ast'];
const printer = printers?.['openapi-ast'];
expect(printer).toBeDefined();
const testData = {
isOpenAPI: true,
format: 'json',
content: {
openapi: '3.0.0',
info: { title: 'Test API', version: '1.0.0' },
@@ -516,7 +531,7 @@ describe('Key Ordering Tests', () => {
}
};
// @ts-expect-error We are mocking things here so we don't need to pass a print function
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 });
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
expect(result).toBeDefined();
if (!result) {
@@ -535,10 +550,12 @@ describe('Key Ordering Tests', () => {
describe('Path sorting', () => {
it('should sort paths by specificity', () => {
const printer = plugin.printers?.['openapi-ast'];
const printer = printers?.['openapi-ast'];
expect(printer).toBeDefined();
const testData = {
isOpenAPI: true,
format: 'json',
content: {
openapi: '3.0.0',
info: { title: 'Test API', version: '1.0.0' },
@@ -551,7 +568,7 @@ describe('Key Ordering Tests', () => {
}
};
// @ts-expect-error We are mocking things here so we don't need to pass a print function
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 });
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
expect(result).toBeDefined();
if (!result) {
@@ -574,10 +591,12 @@ describe('Key Ordering Tests', () => {
describe('Response code sorting', () => {
it('should sort response codes numerically', () => {
const printer = plugin.printers?.['openapi-ast'];
const printer = printers?.['openapi-ast'];
expect(printer).toBeDefined();
const testData = {
isOpenAPI: true,
format: 'json',
content: {
openapi: '3.0.0',
info: { title: 'Test API', version: '1.0.0' },
@@ -597,7 +616,7 @@ describe('Key Ordering Tests', () => {
}
};
// @ts-expect-error We are mocking things here so we don't need to pass a print function
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 });
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
expect(result).toBeDefined();
if (!result) {

View File

@@ -1,105 +0,0 @@
import { describe, it, expect } from 'bun:test';
import plugin from '../src/index';
describe('Plugin Options', () => {
it('should use custom tabWidth for JSON formatting', () => {
const testData = {
openapi: '3.0.0',
info: {
title: 'Test',
version: '1.0.0'
},
paths: {}
};
const printer = plugin.printers?.['openapi-ast'];
expect(printer).toBeDefined();
const result = printer?.print({ getValue: () => ({ type: 'openapi', content: testData, originalText: '', format: 'json' }) }, { tabWidth: 4 }, () => '');
expect(result).toBeDefined();
if (!result) {
throw new Error('Result is undefined');
}
// Check that 4-space indentation is used
expect(result).toContain(' "openapi"');
expect(result).toContain(' "info"');
expect(result).toContain(' "title"');
expect(result).toContain(' "version"');
});
it('should use custom tabWidth for YAML formatting', () => {
const testData = {
openapi: '3.0.0',
info: {
title: 'Test',
version: '1.0.0'
},
paths: {}
};
const printer = plugin.printers?.['openapi-ast'];
expect(printer).toBeDefined();
const result = printer?.print({ getValue: () => ({ type: 'openapi', content: testData, originalText: '', format: 'yaml' }) }, { tabWidth: 4 }, () => '');
expect(result).toBeDefined();
if (!result) {
throw new Error('Result is undefined');
}
// Check that 4-space indentation is used
expect(result).toContain(' title: Test');
expect(result).toContain(' version: 1.0.0');
});
it('should use default tabWidth when not specified', () => {
const testData = {
openapi: '3.0.0',
info: {
title: 'Test',
version: '1.0.0'
},
paths: {}
};
const printer = plugin.printers?.['openapi-ast'];
expect(printer).toBeDefined();
const result = printer?.print({ getValue: () => ({ type: 'openapi', content: testData, originalText: '', format: 'json' }) }, {}, () => '');
expect(result).toBeDefined();
if (!result) {
throw new Error('Result is undefined');
}
// Check that 2-space indentation is used (default)
expect(result).toContain(' "openapi"');
expect(result).toContain(' "info"');
expect(result).toContain(' "title"');
expect(result).toContain(' "version"');
});
it('should use custom printWidth for YAML formatting', () => {
const testData = {
openapi: '3.0.0',
info: {
title: 'This is a very long title that should be wrapped according to printWidth',
version: '1.0.0'
},
paths: {}
};
const printer = plugin.printers?.['openapi-ast'];
expect(printer).toBeDefined();
const result = printer?.print({ getValue: () => ({ type: 'openapi', content: testData, originalText: '', format: 'yaml' }) }, { printWidth: 20 }, () => '');
expect(result).toBeDefined();
// The YAML should be formatted with the custom line width
expect(result).toBeDefined();
// Note: js-yaml doesn't always respect lineWidth for all content types,
// but we can verify the option is passed through
});
});

View File

@@ -32,13 +32,13 @@ describe('Prettier OpenAPI Plugin', () => {
// @ts-expect-error We are mocking things here
const parsed = parser?.parse(inputJson, { filepath: 'test.json' });
expect(parsed).toBeDefined();
expect(parsed?.type).toBe('openapi');
expect(parsed?.isOpenAPI).toBeTrue();
expect(parsed?.format).toBe('json');
expect(parsed?.content).toBeDefined();
// Format the parsed content
// @ts-expect-error We are mocking things here
const result = printer?.print({ getValue: () => parsed }, { tabWidth: 2 }, () => '');
const result = printer?.print({ getNode: () => parsed }, { tabWidth: 2 }, () => '');
expect(result).toBeDefined();
expect(result).toContain('"openapi"');
@@ -80,13 +80,13 @@ openapi: 3.0.0`;
// @ts-expect-error We are mocking things here
const parsed = parser?.parse(inputYaml, { filepath: 'test.yaml' });
expect(parsed).toBeDefined();
expect(parsed?.type).toBe('openapi');
expect(parsed?.isOpenAPI).toBeTrue();
expect(parsed?.format).toBe('yaml');
expect(parsed?.content).toBeDefined();
// Format the parsed content
// @ts-expect-error We are mocking things here
const result = printer?.print({ getValue: () => parsed }, { tabWidth: 2 }, () => '');
const result = printer?.print({ getNode: () => parsed }, { tabWidth: 2 }, () => '');
expect(result).toBeDefined();
expect(result).toContain('openapi:');
@@ -148,13 +148,13 @@ openapi: 3.0.0`;
// @ts-expect-error We are mocking things here
const parsed = parser?.parse(inputJson, { filepath: 'test.json' });
expect(parsed).toBeDefined();
expect(parsed?.type).toBe('openapi');
expect(parsed?.isOpenAPI).toBeTrue();
expect(parsed?.format).toBe('json');
expect(parsed?.content).toBeDefined();
// Format the parsed content
// @ts-expect-error We are mocking things here
const result = printer?.print({ getValue: () => parsed }, { tabWidth: 2 }, () => '');
const result = printer?.print({ getNode: () => parsed }, { tabWidth: 2 }, () => '');
expect(result).toBeDefined();
expect(result).toContain('"swagger"');
@@ -201,13 +201,13 @@ describe('Key Ordering Tests', () => {
// @ts-expect-error We are mocking things here
const parsed = parser?.parse(inputJson, { filepath: 'test.json' });
expect(parsed).toBeDefined();
expect(parsed?.type).toBe('openapi');
expect(parsed?.isOpenAPI).toBeTrue();
expect(parsed?.format).toBe('json');
expect(parsed?.content).toBeDefined();
// Format the parsed content
// @ts-expect-error We are mocking things here
const result = printer?.print({ getValue: () => parsed }, { tabWidth: 2 }, () => '');
const result = printer?.print({ getNode: () => parsed }, { tabWidth: 2 }, () => '');
expect(result).toBeDefined();
@@ -261,13 +261,13 @@ describe('Key Ordering Tests', () => {
// @ts-expect-error We are mocking things here
const parsed = parser?.parse(inputJson, { filepath: 'test.json' });
expect(parsed).toBeDefined();
expect(parsed?.type).toBe('openapi');
expect(parsed?.isOpenAPI).toBeTrue();
expect(parsed?.format).toBe('json');
expect(parsed?.content).toBeDefined();
// Format the parsed content
// @ts-expect-error We are mocking things here
const result = printer?.print({ getValue: () => parsed }, { tabWidth: 2 }, () => '');
const result = printer?.print({ getNode: () => parsed }, { tabWidth: 2 }, () => '');
expect(result).toBeDefined();

View File

@@ -1,12 +1,14 @@
import { describe, expect, it } from 'bun:test';
import plugin from '../src/index';
import { printers } from '../src/index';
describe('Simple Key Ordering Tests', () => {
it('should sort top-level OpenAPI keys correctly', () => {
const printer = plugin.printers?.['openapi-ast'];
const printer = printers?.['openapi-ast'];
expect(printer).toBeDefined();
const testData = {
isOpenAPI: true,
format: 'yaml',
content: {
paths: { '/test': { get: {} } },
components: { schemas: {} },
@@ -19,7 +21,7 @@ describe('Simple Key Ordering Tests', () => {
};
// @ts-expect-error We are mocking things here
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => '');
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => '');
expect(result).toBeDefined();
if (!result) {
@@ -46,10 +48,12 @@ describe('Simple Key Ordering Tests', () => {
});
it('should sort operation keys correctly', () => {
const printer = plugin.printers?.['openapi-ast'];
const printer = printers?.['openapi-ast'];
expect(printer).toBeDefined();
const testData = {
isOpenAPI: true,
format: 'json',
content: {
openapi: '3.0.0',
info: { title: 'Test API', version: '1.0.0' },
@@ -74,7 +78,7 @@ describe('Simple Key Ordering Tests', () => {
};
// @ts-expect-error We are mocking things here
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => '');
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => '');
expect(result).toBeDefined();
if (!result) {
@@ -109,10 +113,12 @@ describe('Simple Key Ordering Tests', () => {
});
it('should sort info keys correctly', () => {
const printer = plugin.printers?.['openapi-ast'];
const printer = printers?.['openapi-ast'];
expect(printer).toBeDefined();
const testData = {
isOpenAPI: true,
format: 'json',
content: {
openapi: '3.0.0',
info: {
@@ -127,7 +133,7 @@ describe('Simple Key Ordering Tests', () => {
};
// @ts-expect-error We are mocking things here
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => '');
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => '');
expect(result).toBeDefined();
if (!result) {
@@ -136,8 +142,6 @@ describe('Simple Key Ordering Tests', () => {
const resultString = result.toString();
console.log(resultString);
// Check that info keys appear in the correct order
const titleIndex = resultString.indexOf('title');
const versionIndex = resultString.indexOf('version');
@@ -154,10 +158,12 @@ describe('Simple Key Ordering Tests', () => {
});
it('should handle custom extensions correctly', () => {
const printer = plugin.printers?.['openapi-ast'];
const printer = printers?.['openapi-ast'];
expect(printer).toBeDefined();
const testData = {
isOpenAPI: true,
format: 'json',
content: {
'x-custom-field': 'value',
'openapi': '3.0.0',
@@ -168,7 +174,8 @@ describe('Simple Key Ordering Tests', () => {
};
// @ts-expect-error We are mocking things here
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => '');
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => '');
console.log('result', result);
expect(result).toBeDefined();
if (!result) {