Skip to content

Commit e3b74e2

Browse files
committed
feat: support dprint
1 parent 74b179e commit e3b74e2

11 files changed

+259
-8
lines changed

README.md

+84-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,77 @@
66
[![JSDocs][jsdocs-src]][jsdocs-href]
77
[![License][license-src]][license-href]
88

9-
Plain text formatters integration for ESLint.
9+
Format various languages with formatters in ESLint. Supports [Prettier](https://prettier.io/) and [dprint](https://dprint.dev/). Side-effects-free and fully configurable.
10+
11+
## Usages
12+
13+
### Install
14+
15+
```bash
16+
npm i -D eslint-plugin-format
17+
```
18+
19+
### Configure
20+
21+
This plugin does not do language detection or reading configure files, you need to specify the language for each file type you want to format along with other formatting options. We recommend using [ESLint's Flat Config format](https://eslint.org/docs/latest/use/configure/configuration-files-new).
22+
23+
```ts
24+
// eslint.config.js
25+
import format from 'eslint-plugin-format'
26+
27+
export default [
28+
// ...other flat configs
29+
30+
// use Prettier to format CSS
31+
{
32+
files: ['**/*.css'],
33+
languageOptions: {
34+
parser: format.parserPlain,
35+
},
36+
plugins: {
37+
format,
38+
},
39+
rules: {
40+
'format/prettier': ['error', { parser: 'css', tabWidth: 2 }],
41+
},
42+
},
43+
44+
// use dprint to format TOML
45+
{
46+
files: ['**/*.toml'],
47+
languageOptions: {
48+
parser: format.parserPlain,
49+
},
50+
plugins: {
51+
format,
52+
},
53+
rules: {
54+
'format/dprint': ['error', { language: 'toml', languageOptions: { indentWidth: 2 } }],
55+
},
56+
},
57+
]
58+
```
59+
60+
## Rules
61+
62+
### `format/prettier`
63+
64+
Use Prettier to format files.
65+
66+
#### Options
67+
68+
- `parser` (required) - the language to format, [Supported languages](https://prettier.io/docs/en/options.html#parser)
69+
- The rest options are passed as Prettier options
70+
71+
### `format/dprint`
72+
73+
Use dprint to format files.
74+
75+
#### Options
76+
77+
- `language` (required) - the language to format, or can be a filepath or URL to the WASM binary. [Supported languages](https://dprint.dev/plugins/)
78+
- `languageOptions` - the options for the language
79+
- The rest options are passed as dprint's general options
1080

1181
## Sponsors
1282

@@ -16,6 +86,19 @@ Plain text formatters integration for ESLint.
1686
</a>
1787
</p>
1888

89+
## FAQ
90+
91+
### What's the difference between this and `eslint-plugin-prettier`?
92+
93+
While this plugin provides Prettier as one of the formatters, the main difference is that `eslint-plugin-prettier` is much more opinionated toward the Prettier CLI ecosystem. While this plugin only treats Prettier as the side-effects-free formatter and gives you full control in ESLint.
94+
95+
## Credits
96+
97+
Thanks to the existing works for references and inspiration.
98+
99+
- [eslint-plugin-prettier](https://github.com/prettier/eslint-plugin-prettier)
100+
- [eslint-plugin-dprint-integration](https://github.com/so1ve/eslint-plugin-dprint-integration)
101+
19102
## License
20103

21104
[MIT](./LICENSE) License © 2023-PRESENT [Anthony Fu](https://github.com/antfu)

dts/define-config-support.d.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import type { RuleOptions } from './rule-options'
2+
3+
declare module 'eslint-define-config' {
4+
export interface CustomRuleOptions extends RuleOptions {}
5+
}
6+
7+
export {}

dts/rule-options.d.ts

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import type { Options as PrettierOptions } from 'prettier'
2+
3+
export interface RuleOptions {
4+
'format/prettier': [PrettierOptions]
5+
'format/dprint': [DprintOptions]
6+
}
7+
8+
export { PrettierOptions }
9+
10+
export interface DprintOptions {
11+
language: string
12+
languageOptions?: Record<string, unknown>
13+
[x: string]: unknown
14+
}

eslint.config.js

+16-4
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,31 @@
11
// @ts-check
22
import antfu from '@antfu/eslint-config'
3-
import formatters from 'eslint-plugin-format'
3+
import format from 'eslint-plugin-format'
44

55
export default antfu(
66
{},
77
{
88
files: ['**/*.css'],
99
languageOptions: {
10-
parser: formatters.parserPlain,
10+
parser: format.parserPlain,
1111
},
1212
plugins: {
13-
formatters,
13+
format,
1414
},
1515
rules: {
16-
'formatters/prettier': ['error', { parser: 'css' }],
16+
'format/prettier': ['error', { parser: 'css' }],
17+
},
18+
},
19+
{
20+
files: ['**/*.toml'],
21+
languageOptions: {
22+
parser: format.parserPlain,
23+
},
24+
plugins: {
25+
format,
26+
},
27+
rules: {
28+
'format/dprint': ['error', { language: 'toml' }],
1729
},
1830
},
1931
)

examples/foo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[foo]
2+
a = 1

examples/style.css

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.foo {
2+
color: green;
3+
}

package.json

+20-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"type": "module",
44
"version": "0.0.0",
55
"packageManager": "[email protected]",
6-
"description": "_description_",
6+
"description": "Format various languages with formatters in ESLint",
77
"author": "Anthony Fu <[email protected]>",
88
"license": "MIT",
99
"funding": "https://github.com/sponsors/antfu",
@@ -13,13 +13,25 @@
1313
"url": "git+https://github.com/antfu/eslint-plugin-format.git"
1414
},
1515
"bugs": "https://github.com/antfu/eslint-plugin-format/issues",
16-
"keywords": [],
16+
"keywords": [
17+
"eslint",
18+
"eslint-plugin",
19+
"formatters",
20+
"prettier",
21+
"dprint"
22+
],
1723
"sideEffects": false,
1824
"exports": {
1925
".": {
2026
"types": "./dist/index.d.ts",
2127
"import": "./dist/index.mjs",
2228
"require": "./dist/index.cjs"
29+
},
30+
"./define-config-support": {
31+
"types": "./dts/define-config-support.d.ts"
32+
},
33+
"./rule-options": {
34+
"types": "./dts/rule-options.d.ts"
2335
}
2436
},
2537
"main": "./dist/index.mjs",
@@ -34,7 +46,9 @@
3446
}
3547
},
3648
"files": [
37-
"dist"
49+
"dist",
50+
"dts",
51+
"workers"
3852
],
3953
"scripts": {
4054
"build": "unbuild",
@@ -51,6 +65,9 @@
5165
"eslint": "^8.40.0"
5266
},
5367
"dependencies": {
68+
"@dprint/formatter": "^0.2.0",
69+
"@dprint/markdown": "^0.16.3",
70+
"@dprint/toml": "^0.5.4",
5471
"eslint-formatting-reporter": "^0.0.0",
5572
"eslint-parser-plain": "^0.1.0",
5673
"prettier": "^3.1.0",

pnpm-lock.yaml

+14
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import * as parserPlain from 'eslint-parser-plain'
22
import prettier from './rules/prettier'
3+
import dprint from './rules/dprint'
34

45
export default {
56
parserPlain,
67
rules: {
78
prettier,
9+
dprint,
810
},
911
}

src/rules/dprint.ts

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { join } from 'node:path'
2+
import type { Rule } from 'eslint'
3+
import { createSyncFn } from 'synckit'
4+
import { messages, reportDifferences } from 'eslint-formatting-reporter'
5+
import { dirWorkers } from '../dir'
6+
import type { DprintOptions } from '../../dts/rule-options'
7+
8+
let format: (code: string, filename: string, options: DprintOptions) => string
9+
10+
export default {
11+
meta: {
12+
type: 'layout',
13+
docs: {
14+
description: 'Use dprint to format code',
15+
category: 'Stylistic',
16+
},
17+
fixable: 'whitespace',
18+
schema: [
19+
{
20+
type: 'object',
21+
properties: {
22+
language: {
23+
type: 'string',
24+
required: true,
25+
},
26+
languageOptions: {
27+
type: 'object',
28+
},
29+
},
30+
additionalProperties: true,
31+
},
32+
],
33+
messages,
34+
},
35+
create(context) {
36+
if (!format)
37+
format = createSyncFn(join(dirWorkers, 'dprint.cjs'))
38+
39+
return {
40+
Program() {
41+
const sourceCode = context.sourceCode.text
42+
const formatted = format(sourceCode, context.filename, context.options[0] || {})
43+
44+
reportDifferences(context, sourceCode, formatted)
45+
},
46+
}
47+
},
48+
} satisfies Rule.RuleModule

workers/dprint.cjs

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
const fs = require('node:fs/promises')
2+
const { Buffer } = require('node:buffer')
3+
const { runAsWorker } = require('synckit')
4+
5+
let dprint
6+
const cache = new Map()
7+
8+
async function loadBuffer(data) {
9+
if (typeof data === 'string' && data.startsWith('@dprint/'))
10+
data = await import(data).then(m => m.getBuffer?.() || m.getPath?.())
11+
12+
if (typeof data === 'string') {
13+
if (data.startsWith('data:')) {
14+
const [, base64] = data.split(',')
15+
return Buffer.from(base64, 'base64')
16+
}
17+
else if (data.match(/^[\w-]+:\/\//)) {
18+
return fetch(data).then(r => r.buffer())
19+
}
20+
else {
21+
return fs.readFile(data)
22+
}
23+
}
24+
25+
return data
26+
}
27+
28+
runAsWorker(async (code, filename, options) => {
29+
if (!dprint)
30+
dprint = await import('@dprint/formatter')
31+
32+
const builtInLangs = {
33+
json: '@dprint/json',
34+
toml: '@dprint/toml',
35+
markdown: '@dprint/markdown',
36+
typescript: '@dprint/typescript',
37+
dockerfile: '@dprint/dockerfile',
38+
}
39+
40+
const lang = builtInLangs[options.language] || options.language
41+
const promise = cache.has(lang)
42+
? cache.get(lang)
43+
: cache.set(lang, loadBuffer(lang).then(r => dprint.createFromBuffer(r))).get(lang)
44+
45+
const formatter = await promise
46+
const { lang: _, languageOptions = {}, ...rest } = options
47+
formatter.setConfig(rest, languageOptions)
48+
return formatter.formatText(filename, code)
49+
})

0 commit comments

Comments
 (0)