Skip to content

Commit c7d449b

Browse files
authored
feat(fmt): support package.json sorting (#139)
1 parent 8cf2d35 commit c7d449b

16 files changed

Lines changed: 201 additions & 47 deletions

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
"cspell-ban-words": "catalog:",
2121
"heading-case": "catalog:",
2222
"prettier": "catalog:",
23-
"prettier-plugin-packagejson": "catalog:",
2423
"rstack": "workspace:*",
2524
"typescript": "catalog:"
2625
},

packages/rstack/THIRD_PARTY_NOTICES.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,57 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
500500
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
501501
DEALINGS IN THE SOFTWARE.
502502

503+
## sort-package-json
504+
505+
This package includes bundled code from
506+
[sort-package-json](https://github.com/keithamus/sort-package-json).
507+
508+
License: MIT
509+
510+
Copyright (c) 2015 Keith Cirkel
511+
512+
The bundled code also contains MIT-licensed code from:
513+
514+
- detect-indent 7.0.2, copyright Sindre Sorhus
515+
- detect-newline 4.0.1, copyright Sindre Sorhus
516+
- git-hooks-list 4.2.1, copyright fisker Cheung
517+
- is-plain-obj 4.1.0, copyright Sindre Sorhus
518+
- sort-object-keys 2.1.0, copyright Keith Cirkel
519+
520+
Permission is hereby granted, free of charge, to any person obtaining a copy
521+
of this software and associated documentation files (the "Software"), to deal
522+
in the Software without restriction, including without limitation the rights
523+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
524+
copies of the Software, and to permit persons to whom the Software is
525+
furnished to do so, subject to the following conditions:
526+
527+
The above copyright notices and this permission notice shall be included in all
528+
copies or substantial portions of the Software.
529+
530+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
531+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
532+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
533+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
534+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
535+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
536+
SOFTWARE.
537+
538+
The bundled code also contains semver 7.8.5, licensed under the ISC License:
539+
540+
Copyright (c) Isaac Z. Schlueter and Contributors
541+
542+
Permission to use, copy, modify, and/or distribute this software for any
543+
purpose with or without fee is hereby granted, provided that the above
544+
copyright notice and this permission notice appear in all copies.
545+
546+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
547+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
548+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
549+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
550+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
551+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
552+
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
553+
503554
## tinyexec
504555

505556
This package includes bundled code from [tinyexec](https://github.com/tinylibs/tinyexec).

packages/rstack/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
"lint-staged": "catalog:",
7878
"micromatch": "catalog:",
7979
"rslog": "catalog:",
80+
"sort-package-json": "catalog:",
8081
"tiny-readdir": "catalog:",
8182
"typescript": "catalog:",
8283
"worktank": "catalog:"

packages/rstack/src/fmt/config.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import { dirname, relative } from 'node:path';
22
import micromatch from 'micromatch';
3-
import type { Options as PrettierOptions } from 'prettier';
4-
import type { FmtConfig, FmtConfigDefinition, ResolvedFmtConfig } from './types.ts';
3+
import type {
4+
FmtConfig,
5+
FmtConfigDefinition,
6+
ResolvedFmtConfig,
7+
ResolvedFmtOptions,
8+
} from './types.ts';
59

610
type ResolveFmtConfigOptions = {
711
definition: FmtConfigDefinition | undefined;
@@ -45,7 +49,7 @@ const pathMatchesGlobs = (
4549
};
4650

4751
/** Applies matching overrides to the shared formatter options. */
48-
const resolveFmtOptions = (filePath: string, config: ResolvedFmtConfig): PrettierOptions => {
52+
const resolveFmtOptions = (filePath: string, config: ResolvedFmtConfig): ResolvedFmtOptions => {
4953
if (config.overrides.length === 0) {
5054
return config.baseOptions;
5155
}

packages/rstack/src/fmt/format.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,20 @@ const formatText = async (
2424
...options,
2525
filepath: filePath,
2626
parser,
27-
plugins: getPrettierPlugins(options.plugins),
2827
};
28+
const plugins = await getPrettierPlugins(formatOptions);
2929

3030
if (cursorOffset === undefined) {
3131
return {
3232
status: 'formatted',
33-
formatted: await format(source, formatOptions),
33+
formatted: await format(source, { ...formatOptions, plugins }),
3434
};
3535
}
3636

3737
const result = await formatWithCursor(source, {
3838
...formatOptions,
3939
cursorOffset,
40+
plugins,
4041
});
4142

4243
return {

packages/rstack/src/fmt/parser.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const resolveFmtParser = async (
1616
(
1717
await getFileInfo(filePath, {
1818
...fileInfoOptions,
19-
plugins: getPrettierPlugins(options.plugins),
19+
plugins: await getPrettierPlugins(options),
2020
})
2121
).inferredParser;
2222

packages/rstack/src/fmt/plugins.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import { isAbsolute, join, resolve as resolvePath } from 'node:path';
22
import { pathToFileURL } from 'node:url';
33
import { moduleResolve } from 'import-meta-resolve';
44
import type { Options as PrettierOptions } from 'prettier';
5-
import type { FmtPluginSpecifier } from './types.ts';
5+
import type { FmtPluginSpecifier, ResolvedFmtOptions } from './types.ts';
66

77
type FmtPlugin = NonNullable<PrettierOptions['plugins']>[number];
8-
type FmtPluginResolver = (options: PrettierOptions) => PrettierOptions;
8+
type FmtPluginResolver = (options: ResolvedFmtOptions) => ResolvedFmtOptions;
99

1010
const resolveModuleUrl = (specifier: string, parentUrl: URL): string =>
1111
moduleResolve(specifier, parentUrl).href;
Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,30 @@
11
import * as yukuPlugin from '@prettier/plugin-yuku';
2-
import type { Options as PrettierOptions } from 'prettier';
2+
import type { Options as PrettierOptions, Plugin } from 'prettier';
3+
import type { ResolvedFmtOptions } from './types.ts';
34

45
type PrettierPlugins = NonNullable<PrettierOptions['plugins']>;
56

6-
const defaultFmtPlugins: PrettierPlugins = [yukuPlugin];
7+
const fmtOptionsPlugin = {
8+
options: {
9+
sortPackageJson: {
10+
category: 'Global',
11+
default: false,
12+
description: 'Sort package.json fields using sort-package-json.',
13+
type: 'boolean',
14+
},
15+
},
16+
} satisfies Plugin;
717

8-
/** Prepends Yuku so project plugins can override the default parser. */
9-
const getPrettierPlugins = (plugins: PrettierOptions['plugins']): PrettierPlugins =>
10-
plugins?.length ? [...defaultFmtPlugins, ...plugins] : defaultFmtPlugins;
18+
const defaultFmtPlugins: PrettierPlugins = [yukuPlugin, fmtOptionsPlugin];
19+
20+
/** Prepends bundled plugins so project plugins can override their parsers. */
21+
const getPrettierPlugins = async (options: ResolvedFmtOptions): Promise<PrettierPlugins> => {
22+
const plugins =
23+
options.sortPackageJson === true && /(^|[/\\])package\.json$/.test(options.filepath ?? '')
24+
? [...defaultFmtPlugins, (await import('./sortPackageJsonPlugin.ts')).sortPackageJsonPlugin]
25+
: defaultFmtPlugins;
26+
27+
return options.plugins?.length ? [...plugins, ...options.plugins] : plugins;
28+
};
1129

1230
export { getPrettierPlugins };

packages/rstack/src/fmt/serial.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const formatFileSerial = async (
1212
const source = await readFile(path, 'utf8');
1313
const formatted = await format(source, {
1414
...options,
15-
plugins: getPrettierPlugins(options.plugins),
15+
plugins: await getPrettierPlugins(options),
1616
});
1717

1818
if (source === formatted) {
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import type { Plugin } from 'prettier';
2+
import { parsers } from 'prettier/plugins/babel';
3+
import sortPackageJson from 'sort-package-json';
4+
5+
const sortPackageJsonPlugin: Plugin = {
6+
parsers: {
7+
'json-stringify': {
8+
...parsers['json-stringify'],
9+
preprocess: (source) => sortPackageJson(source),
10+
},
11+
},
12+
};
13+
14+
export { sortPackageJsonPlugin };

0 commit comments

Comments
 (0)