Skip to content
Open
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
54 changes: 27 additions & 27 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import fs from "node:fs/promises";
import isBinaryPath from "is-binary-path";
import stringify from "json-sorted-stringify";
import os from "node:os";
import path from "node:path";
import process from "node:process";
import Cache from "./cache.js";
Expand All @@ -10,6 +11,7 @@ import { Loaders, File2Loader, getPrettierConfigsMap, getPrettierConfigResolved
import { PRETTIER_VERSION, CLI_VERSION } from "./constants.js";
import Known from "./known.js";
import Logger from "./logger.js";
import { mapSettledWithConcurrency } from "./map_settled_with_concurrency.js";
import { makePrettier } from "./prettier.js";
import {
castArray,
Expand Down Expand Up @@ -179,34 +181,32 @@ async function runGlobs(options: Options, pluginsDefaultOptions: PluginsOptions,
const cache = shouldCache ? new Cache(cacheVersion, projectPath, getCacheRootPath(rootPath), options, stdout) : undefined;
const prettier = await makePrettier(options, cache);

//TODO: Maybe do work in chunks here, as keeping too many formatted files in memory can be a problem
const filesResults = await Promise.allSettled(
filesPathsTargets.map(async (filePath) => {
const isIgnored = () => (ignoreManual ? ignoreManual(filePath) : getIgnoreResolved(filePath, ignoreNames));
const isCacheable = () => cache?.has(filePath, isIgnored);
const isExplicitlyIncluded = () => filesExplicitPathsSet.has(filePath);
const isForceIncluded = options.dump && isExplicitlyIncluded();
const isExcluded = cache ? !(await isCacheable()) : await isIgnored();
if (!isForceIncluded && isExcluded) return;
const getFormatOptions = async (): Promise<FormatOptions> => {
const editorConfig = options.editorConfig ? getEditorConfigFormatOptions(await getEditorConfigResolved(filePath, editorConfigNames)) : {};
const prettierConfig = prettierManualConfig || (options.config ? await getPrettierConfigResolved(filePath, prettierConfigNames) : {});
const formatOptions = { ...editorConfig, ...prettierConfig, ...options.formatOptions };
return formatOptions;
};
try {
if (options.check || options.list) {
return await prettier.checkWithPath(filePath, getFormatOptions, cliContextConfig, pluginsDefaultOptions, pluginsCustomOptions);
} else if (options.write) {
return await prettier.writeWithPath(filePath, getFormatOptions, cliContextConfig, pluginsDefaultOptions, pluginsCustomOptions);
} else {
return await prettier.formatWithPath(filePath, getFormatOptions, cliContextConfig, pluginsDefaultOptions, pluginsCustomOptions);
}
} finally {
spinner?.update(fastRelativePath(rootPath, filePath));
const filesConcurrency = options.parallel ? options.parallelWorkers || Math.max(1, os.cpus().length - 1) : 1;
const filesResults = await mapSettledWithConcurrency(filesPathsTargets, filesConcurrency, async (filePath) => {
const isIgnored = () => (ignoreManual ? ignoreManual(filePath) : getIgnoreResolved(filePath, ignoreNames));
const isCacheable = () => cache?.has(filePath, isIgnored);
const isExplicitlyIncluded = () => filesExplicitPathsSet.has(filePath);
const isForceIncluded = options.dump && isExplicitlyIncluded();
const isExcluded = cache ? !(await isCacheable()) : await isIgnored();
if (!isForceIncluded && isExcluded) return;
const getFormatOptions = async (): Promise<FormatOptions> => {
const editorConfig = options.editorConfig ? getEditorConfigFormatOptions(await getEditorConfigResolved(filePath, editorConfigNames)) : {};
const prettierConfig = prettierManualConfig || (options.config ? await getPrettierConfigResolved(filePath, prettierConfigNames) : {});
const formatOptions = { ...editorConfig, ...prettierConfig, ...options.formatOptions };
return formatOptions;
};
try {
if (options.check || options.list) {
return await prettier.checkWithPath(filePath, getFormatOptions, cliContextConfig, pluginsDefaultOptions, pluginsCustomOptions);
} else if (options.write) {
return await prettier.writeWithPath(filePath, getFormatOptions, cliContextConfig, pluginsDefaultOptions, pluginsCustomOptions);
} else {
return await prettier.formatWithPath(filePath, getFormatOptions, cliContextConfig, pluginsDefaultOptions, pluginsCustomOptions);
}
}),
);
} finally {
spinner?.update(fastRelativePath(rootPath, filePath));
}
});

spinner?.stop("Checking formatting...");

Expand Down
31 changes: 31 additions & 0 deletions src/map_settled_with_concurrency.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
async function mapSettledWithConcurrency<T, R>(
values: readonly T[],
concurrency: number,
mapper: (value: T, index: number) => Promise<R>,
): Promise<PromiseSettledResult<R>[]> {
const results = new Array<PromiseSettledResult<R>>(values.length);
let nextIndex = 0;

async function runNext(): Promise<void> {
while (true) {
const index = nextIndex++;
if (index >= values.length) return;

try {
results[index] = {
status: "fulfilled",
value: await mapper(values[index], index),
};
} catch (reason) {
results[index] = { status: "rejected", reason };
}
}
}

const runnersCount = Math.min(values.length, Math.max(1, Math.floor(concurrency)));
await Promise.all(Array.from({ length: runnersCount }, runNext));

return results;
}

export { mapSettledWithConcurrency };
22 changes: 22 additions & 0 deletions test/__tests__/map-settled-with-concurrency.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { expect, test } from "@jest/globals";
import { mapSettledWithConcurrency } from "../../dist/map_settled_with_concurrency.js";

test("bounds concurrency and preserves settled-result order", async () => {
let active = 0;
let maxActive = 0;

const results = await mapSettledWithConcurrency([0, 1, 2, 3, 4, 5], 3, async (value) => {
active += 1;
maxActive = Math.max(maxActive, active);
await new Promise((resolve) => setImmediate(resolve));
active -= 1;
if (value === 2) throw new Error("expected failure");
return value * 2;
});

expect(maxActive).toBe(3);
expect(results.map((result) => result.status)).toEqual(["fulfilled", "fulfilled", "rejected", "fulfilled", "fulfilled", "fulfilled"]);
expect(results[0]).toEqual({ status: "fulfilled", value: 0 });
expect(results[2]).toMatchObject({ status: "rejected", reason: new Error("expected failure") });
expect(results[5]).toEqual({ status: "fulfilled", value: 10 });
});