diff --git a/packages/types/src/namespace.test-d.ts b/packages/types/src/namespace.test-d.ts new file mode 100644 index 0000000..950be57 --- /dev/null +++ b/packages/types/src/namespace.test-d.ts @@ -0,0 +1,158 @@ +import type * as OpenAPIV3_0 from '@openapi-spec/types/v3.0' +import type * as OpenAPIV3_1 from '@openapi-spec/types/v3.1' +import type * as OpenAPIV3_2 from '@openapi-spec/types/v3.2' + +// Every export of every version entry, used as a type through a namespace +// import. A member that exists only as a value fails with TS2749, the way +// `OpenAPIV3_1.TagObject` did in 0.1.0. `namespace.test.ts` fails when an +// export is missing from these lists. + +export type V3_0Members = [ + OpenAPIV3_0.ApiKeySecuritySchemeObject, + OpenAPIV3_0.AuthorizationCodeOAuthFlowObject, + OpenAPIV3_0.CallbackObject, + OpenAPIV3_0.ClientCredentialsOAuthFlowObject, + OpenAPIV3_0.ComponentsObject, + OpenAPIV3_0.ContactObject, + OpenAPIV3_0.DiscriminatorObject, + OpenAPIV3_0.EncodingObject, + OpenAPIV3_0.ExampleObject, + OpenAPIV3_0.ExternalDocumentationObject, + OpenAPIV3_0.HeaderObject, + OpenAPIV3_0.HttpSecuritySchemeObject, + OpenAPIV3_0.ImplicitOAuthFlowObject, + OpenAPIV3_0.InfoObject, + OpenAPIV3_0.LicenseObject, + OpenAPIV3_0.LinkObject, + OpenAPIV3_0.MediaTypeObject, + OpenAPIV3_0.OAuth2SecuritySchemeObject, + OpenAPIV3_0.OAuthFlowObject, + OpenAPIV3_0.OAuthFlowObjectBase, + OpenAPIV3_0.OAuthFlowsObject, + OpenAPIV3_0.OpenAPIObject, + OpenAPIV3_0.OpenIdConnectSecuritySchemeObject, + OpenAPIV3_0.OperationObject, + OpenAPIV3_0.ParameterLocation, + OpenAPIV3_0.ParameterObject, + OpenAPIV3_0.ParameterStyle, + OpenAPIV3_0.PasswordOAuthFlowObject, + OpenAPIV3_0.PathItemObject, + OpenAPIV3_0.PathsObject, + OpenAPIV3_0.QueryParameterStyle, + OpenAPIV3_0.ReferenceObject, + OpenAPIV3_0.RequestBodyObject, + OpenAPIV3_0.ResponseObject, + OpenAPIV3_0.ResponsesObject, + OpenAPIV3_0.SchemaObject, + OpenAPIV3_0.SchemaObjectType, + OpenAPIV3_0.SecurityRequirementObject, + OpenAPIV3_0.SecuritySchemeObject, + OpenAPIV3_0.SecuritySchemeType, + OpenAPIV3_0.ServerObject, + OpenAPIV3_0.ServerVariableObject, + OpenAPIV3_0.SpecificationExtensions, + OpenAPIV3_0.TagObject, + OpenAPIV3_0.XMLObject, +] + +export type V3_1Members = [ + OpenAPIV3_1.ApiKeySecuritySchemeObject, + OpenAPIV3_1.AuthorizationCodeOAuthFlowObject, + OpenAPIV3_1.CallbackObject, + OpenAPIV3_1.ClientCredentialsOAuthFlowObject, + OpenAPIV3_1.ComponentsObject, + OpenAPIV3_1.ContactObject, + OpenAPIV3_1.DiscriminatorObject, + OpenAPIV3_1.EncodingObject, + OpenAPIV3_1.ExampleObject, + OpenAPIV3_1.ExternalDocumentationObject, + OpenAPIV3_1.HeaderObject, + OpenAPIV3_1.HttpSecuritySchemeObject, + OpenAPIV3_1.ImplicitOAuthFlowObject, + OpenAPIV3_1.InfoObject, + OpenAPIV3_1.LicenseObject, + OpenAPIV3_1.LinkObject, + OpenAPIV3_1.MediaTypeObject, + OpenAPIV3_1.MutualTlsSecuritySchemeObject, + OpenAPIV3_1.OAuth2SecuritySchemeObject, + OpenAPIV3_1.OAuthFlowObject, + OpenAPIV3_1.OAuthFlowObjectBase, + OpenAPIV3_1.OAuthFlowsObject, + OpenAPIV3_1.OpenAPIObject, + OpenAPIV3_1.OpenIdConnectSecuritySchemeObject, + OpenAPIV3_1.OperationObject, + OpenAPIV3_1.ParameterLocation, + OpenAPIV3_1.ParameterObject, + OpenAPIV3_1.ParameterStyle, + OpenAPIV3_1.PasswordOAuthFlowObject, + OpenAPIV3_1.PathItemObject, + OpenAPIV3_1.PathsObject, + OpenAPIV3_1.QueryParameterStyle, + OpenAPIV3_1.ReferenceObject, + OpenAPIV3_1.RequestBodyObject, + OpenAPIV3_1.ResponseObject, + OpenAPIV3_1.ResponsesObject, + OpenAPIV3_1.SchemaObject, + OpenAPIV3_1.SchemaObjectFields, + OpenAPIV3_1.SchemaObjectType, + OpenAPIV3_1.SecurityRequirementObject, + OpenAPIV3_1.SecuritySchemeObject, + OpenAPIV3_1.SecuritySchemeType, + OpenAPIV3_1.ServerObject, + OpenAPIV3_1.ServerVariableObject, + OpenAPIV3_1.SpecificationExtensions, + OpenAPIV3_1.TagObject, + OpenAPIV3_1.XMLObject, +] + +export type V3_2Members = [ + OpenAPIV3_2.ApiKeySecuritySchemeObject, + OpenAPIV3_2.AuthorizationCodeOAuthFlowObject, + OpenAPIV3_2.CallbackObject, + OpenAPIV3_2.ClientCredentialsOAuthFlowObject, + OpenAPIV3_2.ComponentsObject, + OpenAPIV3_2.ContactObject, + OpenAPIV3_2.DeviceAuthorizationOAuthFlowObject, + OpenAPIV3_2.DiscriminatorObject, + OpenAPIV3_2.EncodingObject, + OpenAPIV3_2.ExampleObject, + OpenAPIV3_2.ExternalDocumentationObject, + OpenAPIV3_2.HeaderObject, + OpenAPIV3_2.HttpSecuritySchemeObject, + OpenAPIV3_2.ImplicitOAuthFlowObject, + OpenAPIV3_2.InfoObject, + OpenAPIV3_2.LicenseObject, + OpenAPIV3_2.LinkObject, + OpenAPIV3_2.MediaTypeObject, + OpenAPIV3_2.MutualTlsSecuritySchemeObject, + OpenAPIV3_2.OAuth2SecuritySchemeObject, + OpenAPIV3_2.OAuthFlowObject, + OpenAPIV3_2.OAuthFlowObjectBase, + OpenAPIV3_2.OAuthFlowsObject, + OpenAPIV3_2.OpenAPIObject, + OpenAPIV3_2.OpenIdConnectSecuritySchemeObject, + OpenAPIV3_2.OperationObject, + OpenAPIV3_2.ParameterLocation, + OpenAPIV3_2.ParameterObject, + OpenAPIV3_2.ParameterStyle, + OpenAPIV3_2.PasswordOAuthFlowObject, + OpenAPIV3_2.PathItemObject, + OpenAPIV3_2.PathsObject, + OpenAPIV3_2.QueryParameterStyle, + OpenAPIV3_2.ReferenceObject, + OpenAPIV3_2.RequestBodyObject, + OpenAPIV3_2.ResponseObject, + OpenAPIV3_2.ResponsesObject, + OpenAPIV3_2.SchemaObject, + OpenAPIV3_2.SchemaObjectFields, + OpenAPIV3_2.SchemaObjectType, + OpenAPIV3_2.SecurityRequirementObject, + OpenAPIV3_2.SecuritySchemeObject, + OpenAPIV3_2.SecuritySchemeType, + OpenAPIV3_2.ServerObject, + OpenAPIV3_2.ServerVariableObject, + OpenAPIV3_2.SpecificationExtensions, + OpenAPIV3_2.TagObject, + OpenAPIV3_2.XMLNodeType, + OpenAPIV3_2.XMLObject, +] diff --git a/packages/types/src/namespace.test.ts b/packages/types/src/namespace.test.ts new file mode 100644 index 0000000..6b868ac --- /dev/null +++ b/packages/types/src/namespace.test.ts @@ -0,0 +1,49 @@ +import { readFileSync } from 'node:fs' +import { fileURLToPath } from 'node:url' +import ts from 'typescript' + +const testFile = fileURLToPath(new URL('./namespace.test-d.ts', import.meta.url)) +const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')) + +const program = ts.createProgram([testFile], { + module: ts.ModuleKind.ESNext, + moduleResolution: ts.ModuleResolutionKind.Bundler, + noEmit: true, +}) +const checker = program.getTypeChecker() +const source = program.getSourceFile(testFile)! + +const namespaces = new Map }>() +const specifiers: string[] = [] + +for (const statement of source.statements) { + const bindings = ts.isImportDeclaration(statement) && statement.importClause?.namedBindings + if (bindings && ts.isNamespaceImport(bindings)) { + const specifier = statement.moduleSpecifier as ts.StringLiteral + const module = checker.getSymbolAtLocation(specifier)! + specifiers.push(specifier.text) + namespaces.set(bindings.name.text, { + exported: checker.getExportsOfModule(module).map(symbol => symbol.name).sort(), + referenced: new Set(), + }) + } +} + +source.forEachChild(function visit(node) { + if (ts.isQualifiedName(node) && ts.isIdentifier(node.left)) + namespaces.get(node.left.text)?.referenced.add(node.right.text) + node.forEachChild(visit) +}) + +it('imports every version entry as a namespace', () => { + const entries = Object.keys(pkg.exports) + .filter(entry => entry !== './package.json') + .map(entry => `${pkg.name}/${entry.slice(2)}`) + + expect(specifiers.sort()).toEqual(entries.sort()) +}) + +it.each([...namespaces])('references every %s member as a type', (_, { exported, referenced }) => { + expect(exported.length).toBeGreaterThan(0) + expect([...referenced].sort()).toEqual(exported) +})