Skip to content

Commit

Permalink
fix: Fix option parsing and types
Browse files Browse the repository at this point in the history
The option types wrongly declared `jsonSortOrder` as a required option,
resulting in a type mismatch. If no `jsonSortOrder` was specified, it
would be typed as `Record<..>` despite having the value `null`. This
had no runtime functional impact but it was still wrong and confusing.

The option parsing has been updated to better preserve type safety, and
the types have been updated to differentiate pre-processed options,
processed options, and options with defaults applied at runtime.
  • Loading branch information
Gudahtt committed Jan 6, 2025
1 parent f835976 commit 3d02668
Showing 1 changed file with 58 additions and 25 deletions.
83 changes: 58 additions & 25 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,54 +253,68 @@ function sortAst(
* JSON sorting options. See README for details.
*/
type SortJsonOptions = {
jsonRecursiveSort: boolean;
jsonSortOrder: Record<string, CategorySort | null>;
jsonRecursiveSort?: boolean;
jsonSortOrder?: Record<string, CategorySort | null>;
};

/**
* JSON sorting options after they have been processed by Prettier.
*
* Defaults set in the 'options' export have been applied by this point.
*/
type ProcessedSortJsonOptions = Omit<SortJsonOptions, 'jsonRecursiveSort'> &
Required<Pick<SortJsonOptions, 'jsonRecursiveSort'>>;

/**
* Parse JSON sort options from Prettier options.
*
* @param prettierOptions - Prettier options.
* @returns JSON sort options.
*/
function parseOptions(prettierOptions: ParserOptions): SortJsonOptions {
const { jsonRecursiveSort } = prettierOptions;

function parseOptions(
prettierOptions: ParserOptions,
): ProcessedSortJsonOptions {
// Unreachable, validated before here by Prettier
/* c8 ignore start */
if (typeof jsonRecursiveSort !== 'boolean') {
if (typeof prettierOptions.jsonRecursiveSort !== 'boolean') {
throw new Error(
`Invalid 'jsonRecursiveSort' option; expected boolean, got '${typeof prettierOptions.jsonRecursiveSort}'`,
);
}
/* c8 ignore stop */
const parsedJsonSortOptions: ProcessedSortJsonOptions = {
jsonRecursiveSort: prettierOptions.jsonRecursiveSort,
};

const rawJsonSortOrder = prettierOptions.jsonSortOrder ?? null;
// Unreachable, validated before here by Prettier
/* c8 ignore start */
if (rawJsonSortOrder !== null && typeof rawJsonSortOrder !== 'string') {
throw new Error(
`Invalid 'jsonSortOrder' option; expected string, got '${typeof prettierOptions.rawJsonSortOrder}'`,
);
}
/* c8 ignore stop */
if ('jsonSortOrder' in prettierOptions) {
const rawJsonSortOrder = prettierOptions.jsonSortOrder;
// Unreachable, validated before here by Prettier
/* c8 ignore start */
if (typeof rawJsonSortOrder !== 'string') {
throw new Error(
`Invalid 'jsonSortOrder' option; expected string, got '${typeof prettierOptions.rawJsonSortOrder}'`,
);
}
/* c8 ignore stop */

let jsonSortOrder = null;
if (rawJsonSortOrder !== null) {
let parsedJsonSortOrder;
try {
jsonSortOrder = JSON.parse(rawJsonSortOrder);
parsedJsonSortOrder = JSON.parse(rawJsonSortOrder);
} catch (error) {
// @ts-expect-error Error cause property not yet supported by '@types/node' (see https://github.com/DefinitelyTyped/DefinitelyTyped/pull/61827)
throw new Error(`Failed to parse sort order option as JSON`, {
cause: error,
});
}

if (Array.isArray(jsonSortOrder) || typeof jsonSortOrder !== 'object') {
if (
Array.isArray(parsedJsonSortOrder) ||
typeof parsedJsonSortOrder !== 'object'
) {
throw new Error(`Invalid custom sort order; must be an object`);
}

for (const categorySort of Object.values(jsonSortOrder)) {
for (const categorySort of Object.values(parsedJsonSortOrder)) {
if (
(categorySort !== null && typeof categorySort !== 'string') ||
!allowedCategorySortValues.includes(categorySort)
Expand All @@ -312,9 +326,28 @@ function parseOptions(prettierOptions: ParserOptions): SortJsonOptions {
);
}
}

parsedJsonSortOptions.jsonSortOrder = parsedJsonSortOrder;
}

return { jsonRecursiveSort, jsonSortOrder };
return parsedJsonSortOptions;
}

/**
* Apply default sort options.
*
* @param options - JSON sort options as configured.
* @returns JSON sort options with defaults applied.
*/
function applyDefaultOptions(
options: ProcessedSortJsonOptions,
): Required<ProcessedSortJsonOptions> {
const { jsonRecursiveSort, jsonSortOrder } = options;

return {
jsonRecursiveSort, // Default already applied by Prettier
jsonSortOrder: jsonSortOrder ?? {},
};
}

/**
Expand Down Expand Up @@ -395,7 +428,9 @@ function createParser(
parser: JsonParser,
): (text: string, options: ParserOptions) => Promise<any> {
return async (text: string, prettierOptions: ParserOptions): Promise<any> => {
const { jsonRecursiveSort, jsonSortOrder } = parseOptions(prettierOptions);
const { jsonRecursiveSort, jsonSortOrder } = applyDefaultOptions(
parseOptions(prettierOptions),
);

const jsonRootAst = await babelParsers[parser].parse(text, prettierOptions);

Expand All @@ -416,9 +451,7 @@ function createParser(
}

let sortCompareFunction: (a: string, b: string) => number = lexicalSort;
if (jsonSortOrder) {
sortCompareFunction = createSortCompareFunction(jsonSortOrder);
}
sortCompareFunction = createSortCompareFunction(jsonSortOrder);
const sortedAst = sortAst(ast, jsonRecursiveSort, sortCompareFunction);

return {
Expand Down

0 comments on commit 3d02668

Please sign in to comment.