Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions __tests__/__snapshots__/query-serializer.test.ts.snap

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion __tests__/fixtures/docker/.openapi-codegen-manifest.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion __tests__/fixtures/openai/.openapi-codegen-manifest.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion __tests__/fixtures/petstore/.openapi-codegen-manifest.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion __tests__/fixtures/test1/.openapi-codegen-manifest.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 9 additions & 6 deletions __tests__/lint-directives.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ const withHeader = {
},
};

async function buildIn(parent: string, source: object = document) {
async function buildIntoTempDir(parent: string, source: object = document) {
const dir = await mkdtemp(path.join(parent, "lint-directives-"));
const input = path.join(dir, "document.json");

Expand All @@ -67,7 +67,7 @@ async function buildIn(parent: string, source: object = document) {

// Output inside this repository finds its oxlint
test("a file names the exempt rules that fire in it", async () => {
const { dir, valibot, main } = await buildIn(import.meta.dirname);
const { dir, valibot, main } = await buildIntoTempDir(import.meta.dirname);

await rm(dir, { recursive: true });

Expand All @@ -80,7 +80,7 @@ test("a file names the exempt rules that fire in it", async () => {
// An open object can let a peer's unnamed keys through, so its error reaches
// the consumer
test("an open object schema stays a lint error", async () => {
const { dir, valibot } = await buildIn(import.meta.dirname);
const { dir, valibot } = await buildIntoTempDir(import.meta.dirname);

await rm(dir, { recursive: true });

Expand Down Expand Up @@ -124,7 +124,10 @@ async function strictObjectErrors(dir: string) {
// Stripping undeclared headers is the point of a header schema, so only the
// open body object reaches the consumer's lint
test("a header schema is bracketed and an open body object still errors", async () => {
const { dir, valibot } = await buildIn(import.meta.dirname, withHeader);
const { dir, valibot } = await buildIntoTempDir(
import.meta.dirname,
withHeader,
);
const errors = await strictObjectErrors(dir);

await rm(dir, { recursive: true });
Expand All @@ -143,7 +146,7 @@ test("a header schema is bracketed and an open body object still errors", async
// An upgrade from 12, or a changed lint config, leaves a file its manifest
// records as current without the directives it now needs
test("a regeneration writes directives the recorded files lack", async () => {
const { dir } = await buildIn(import.meta.dirname);
const { dir } = await buildIntoTempDir(import.meta.dirname);
const manifestPath = path.join(dir, ".openapi-codegen-manifest.json");
const manifestText = await readFile(manifestPath, "utf8");
const manifest: unknown = JSON.parse(manifestText);
Expand Down Expand Up @@ -201,7 +204,7 @@ test("no fixture directive names snake-case-wire-keys", async () => {
});

test("output outside any oxlint project gets no directive", async () => {
const { dir, valibot } = await buildIn(tmpdir());
const { dir, valibot } = await buildIntoTempDir(tmpdir());

await rm(dir, { recursive: true });

Expand Down
6 changes: 3 additions & 3 deletions __tests__/query-roundtrip.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ function createHonoApp(middleware: readonly MiddlewareHandler[]) {
}

// Collects what the generator says while it walks a document
async function warningsFrom(parameters: readonly oas32.ParameterObject[]) {
async function collectWarnings(parameters: readonly oas32.ParameterObject[]) {
const warnings: string[] = [];
const original = console.warn;
console.warn = (message: string) => warnings.push(message);
Expand Down Expand Up @@ -141,7 +141,7 @@ const rangeSchema: oas32.SchemaObject = {
// worth saying out loud
test("an object query parameter with no declared style is warned about", async () => {
await expect(
warningsFrom([{ name: "at", in: "query", schema: rangeSchema }]),
collectWarnings([{ name: "at", in: "query", schema: rangeSchema }]),
).resolves.toContain(
'query parameter "at" is an object but declares no `style`',
);
Expand All @@ -151,7 +151,7 @@ test("an object query parameter with no declared style is warned about", async (
// every encoding this generator could pick keeps them alike
test("two default-style object parameters sharing a member name are warned about", async () => {
await expect(
warningsFrom([
collectWarnings([
{ name: "created", in: "query", schema: rangeSchema },
{ name: "updated", in: "query", schema: rangeSchema },
]),
Expand Down
72 changes: 16 additions & 56 deletions __tests__/query-serializer.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { RestServiceClient } from "@block65/rest-client";
import type { oas32 } from "openapi3-ts";
import { assert, expect, test, vi } from "vitest";
import { assert, expect, test } from "vitest";
import { ImageCreateCommand } from "./fixtures/docker/commands.ts";
import { FindPetsCommand } from "./fixtures/petstore/commands.ts";
import { SwaggerPetstoreRestClient } from "./fixtures/petstore/main.ts";
import {
generateCommandsText,
generateWithQueryParameters,
Expand Down Expand Up @@ -126,63 +124,25 @@ test("the source of a command that names none", async () => {
).resolves.toMatchSnapshot();
});

function fetchStub() {
return vi.fn<typeof globalThis.fetch>(async () => Response.json({}));
}

function urlFrom(fetch: ReturnType<typeof fetchStub>) {
expect(fetch).toHaveBeenCalledOnce();

const [url] = fetch.mock.calls[0] ?? [];
assert(url instanceof URL);

return url.href;
}

// findPets declares tags before limit, which is not alphabetical order
async function findPetsUrl(sortQuery?: true) {
const fetch = fetchStub();
const client = new SwaggerPetstoreRestClient(
new URL("https://example.com/api/"),
{ fetch, sortQuery },
);

await client.json(new FindPetsCommand({ tags: ["cat", "dog"], limit: "10" }));

return urlFrom(fetch);
}
const findPets = new FindPetsCommand({ tags: ["cat", "dog"], limit: "10" });

// ImageCreate declares seven query parameters and names formJoinSerializer
async function imageCreateUrl(sortQuery?: true) {
const fetch = fetchStub();

// ImageCreate leaves its response unspecified, so the base client sends it
const client = new RestServiceClient(new URL("https://example.com/v1.43/"), {
fetch,
sortQuery,
});

await client.json(
new ImageCreateCommand({
body: "",
tag: "latest",
fromImage: "alpine",
changes: ["ENV a=1", "ENV b=2"],
platform: "linux/amd64",
}),
);

return urlFrom(fetch);
}
const imageCreate = new ImageCreateCommand({
body: "",
tag: "latest",
fromImage: "alpine",
changes: ["ENV a=1", "ENV b=2"],
platform: "linux/amd64",
});

// A change in how the constructor assembles the query — the destructuring,
// stripUndefined, a spread — moves these keys
test("the generated query reaches the URL in document order", async () => {
await expect(findPetsUrl()).resolves.toMatchSnapshot("findPets");
await expect(imageCreateUrl()).resolves.toMatchSnapshot("imageCreate");
});

test("sortQuery orders the generated query in the URL", async () => {
await expect(findPetsUrl(true)).resolves.toMatchSnapshot("findPets");
await expect(imageCreateUrl(true)).resolves.toMatchSnapshot("imageCreate");
test("the generated query serializes in document order", () => {
expect(findPets.querySerializer(findPets.query ?? {})).toMatchSnapshot(
"findPets",
);
expect(imageCreate.querySerializer(imageCreate.query ?? {})).toMatchSnapshot(
"imageCreate",
);
});
16 changes: 8 additions & 8 deletions lib/chunks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { SourceFile } from "ts-morph";
// an insert re-parses its whole chunk, so a small chunk keeps inserts cheap
const chunkSize = 10;

const chunksOf = new WeakMap<SourceFile, SourceFile[]>();
const chunksByFile = new WeakMap<SourceFile, SourceFile[]>();

/**
* ts-morph re-parses a whole file on every insert, so a module of thousands
Expand All @@ -12,8 +12,8 @@ const chunksOf = new WeakMap<SourceFile, SourceFile[]>();
* A statement declared while building the arguments of another can start a
* new chunk and land after it, so declare it first
*/
export function chunkOf(file: SourceFile) {
const chunks = chunksOf.get(file) ?? [];
export function openChunk(file: SourceFile) {
const chunks = chunksByFile.get(file) ?? [];
const last = chunks.at(-1);

if (last && last.getStatements().length < chunkSize) {
Expand All @@ -27,13 +27,13 @@ export function chunkOf(file: SourceFile) {
});

chunks.push(chunk);
chunksOf.set(file, chunks);
chunksByFile.set(file, chunks);

return chunk;
}

// the gap ts-morph left between statements, blank only between classes
function separatorIn(chunk: SourceFile) {
function readStatementSeparator(chunk: SourceFile) {
const [before, last] = chunk.getStatements().slice(-2);

return before && last
Expand All @@ -46,15 +46,15 @@ function separatorIn(chunk: SourceFile) {
* before anything reads the module back
*/
export function joinChunks(file: SourceFile) {
const chunks = chunksOf.get(file) ?? [];
const chunks = chunksByFile.get(file) ?? [];

if (chunks.length > 0) {
const text = chunks
.map((chunk, index) => {
const next = chunks[index + 1];
const body = chunk.getFullText().trim();

return next ? body + separatorIn(chunk) : body;
return next ? body + readStatementSeparator(chunk) : body;
})
.join("");

Expand All @@ -70,5 +70,5 @@ export function joinChunks(file: SourceFile) {
chunk.forget();
}

chunksOf.delete(file);
chunksByFile.delete(file);
}
Loading
Loading