diff --git a/fixtures/output/with-formatters/jsx.jsx b/fixtures/output/with-formatters/jsx.jsx
index 17392c70b3..4f287e4567 100644
--- a/fixtures/output/with-formatters/jsx.jsx
+++ b/fixtures/output/with-formatters/jsx.jsx
@@ -9,7 +9,7 @@ export function HelloWorld({
// TODO: Don't use random in render
const num = Math.floor (Math.random() * 1e+7).toString()
- .replace(/\.\d+/ig, '')
+ .replace(/\.\d+/g, '')
return (
diff --git a/fixtures/output/with-formatters/xml.xml b/fixtures/output/with-formatters/xml.xml
new file mode 100644
index 0000000000..88571e3d82
--- /dev/null
+++ b/fixtures/output/with-formatters/xml.xml
@@ -0,0 +1,20 @@
+
+
+ Effective Java
+ 45.00
+
+
+ Bluetooth Speaker
+ 120.00
+
+
+ Clean Code
+ 33.50
+
+
+
+
+
+
+
+
diff --git a/package.json b/package.json
index b56a5e9d81..57e81bfaab 100644
--- a/package.json
+++ b/package.json
@@ -40,6 +40,7 @@
},
"peerDependencies": {
"@eslint-react/eslint-plugin": "^1.5.8",
+ "@prettier/plugin-xml": "^3.4.1",
"@unocss/eslint-plugin": ">=0.50.0",
"astro-eslint-parser": "^0.16.3",
"eslint": ">=8.40.0",
@@ -57,6 +58,9 @@
"@eslint-react/eslint-plugin": {
"optional": true
},
+ "@prettier/plugin-xml": {
+ "optional": true
+ },
"@unocss/eslint-plugin": {
"optional": true
},
@@ -133,6 +137,7 @@
"@antfu/ni": "^0.21.12",
"@eslint-react/eslint-plugin": "^1.5.11",
"@eslint/config-inspector": "^0.4.8",
+ "@prettier/plugin-xml": "^3.4.1",
"@stylistic/eslint-plugin-migrate": "^2.1.0",
"@types/eslint": "^8.56.10",
"@types/fs-extra": "^11.0.4",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 85207a23c3..a3943bec2e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -126,6 +126,9 @@ importers:
'@eslint/config-inspector':
specifier: ^0.4.8
version: 0.4.8(eslint-ts-patch@9.2.0-6)
+ '@prettier/plugin-xml':
+ specifier: ^3.4.1
+ version: 3.4.1(prettier@3.2.5)
'@stylistic/eslint-plugin-migrate':
specifier: ^2.1.0
version: 2.1.0(eslint-ts-patch@9.2.0-6)(typescript@5.4.5)
@@ -712,6 +715,11 @@ packages:
resolution: {integrity: sha512-Zwq5OCzuwJC2jwqmpEQt7Ds1DTi6BWSwoGkbb1n9pO3hzb35BoJELx7c0T23iDkBGkh2e7tvOtjF3tr3OaQHDQ==}
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
+ '@prettier/plugin-xml@3.4.1':
+ resolution: {integrity: sha512-Uf/6/+9ez6z/IvZErgobZ2G9n1ybxF5BhCd7eMcKqfoWuOzzNUxBipNo3QAP8kRC1VD18TIo84no7LhqtyDcTg==}
+ peerDependencies:
+ prettier: ^3.0.0
+
'@rollup/rollup-android-arm-eabi@4.4.1':
resolution: {integrity: sha512-Ss4suS/sd+6xLRu+MLCkED2mUrAyqHmmvZB+zpzZ9Znn9S8wCkTQCJaQ8P8aHofnvG5L16u9MVnJjCqioPErwQ==}
cpu: [arm]
@@ -1015,6 +1023,9 @@ packages:
'@vue/shared@3.4.27':
resolution: {integrity: sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==}
+ '@xml-tools/parser@1.0.11':
+ resolution: {integrity: sha512-aKqQ077XnR+oQtHJlrAflaZaL7qZsulWc/i/ZEooar5JiWj1eLt0+Wg28cpa+XLney107wXqneC+oG1IZvxkTA==}
+
acorn-jsx@5.3.2:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies:
@@ -1186,6 +1197,9 @@ packages:
check-error@1.0.3:
resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==}
+ chevrotain@7.1.1:
+ resolution: {integrity: sha512-wy3mC1x4ye+O+QkEinVJkPf5u2vsrDIYW9G7ZuwFl6v/Yu0LwUuT2POsb+NUWApebyxfkQq6+yDfRExbnI5rcw==}
+
chokidar@3.6.0:
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
engines: {node: '>= 8.10.0'}
@@ -1912,8 +1926,8 @@ packages:
resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
engines: {node: '>=6'}
- importx@0.2.0:
- resolution: {integrity: sha512-a1WsQz4efNJGPPaQkcMioCQ1ttRZlJh2e53ivJUuR/LvjYW0cCqe2m6gcBqj3U+QyG7KU5zLkkZ2CtObNKLhXA==}
+ importx@0.2.2:
+ resolution: {integrity: sha512-JPVUEEo8iNfxG3t8+ZCCk/vaI0RlCrw6sH8cdE8G9sqo7Kolq+ApF2SZXsuWmLZi3Y60GCTwwttIDQP0dbhuwA==}
imurmurhash@0.1.4:
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
@@ -2571,6 +2585,9 @@ packages:
resolution: {integrity: sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==}
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
+ regexp-to-ast@0.5.0:
+ resolution: {integrity: sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==}
+
regexp-tree@0.1.27:
resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==}
hasBin: true
@@ -3579,6 +3596,11 @@ snapshots:
'@pkgr/core@0.1.0': {}
+ '@prettier/plugin-xml@3.4.1(prettier@3.2.5)':
+ dependencies:
+ '@xml-tools/parser': 1.0.11
+ prettier: 3.2.5
+
'@rollup/rollup-android-arm-eabi@4.4.1':
optional: true
@@ -3964,6 +3986,10 @@ snapshots:
'@vue/shared@3.4.27': {}
+ '@xml-tools/parser@1.0.11':
+ dependencies:
+ chevrotain: 7.1.1
+
acorn-jsx@5.3.2(acorn@8.11.3):
dependencies:
acorn: 8.11.3
@@ -4155,6 +4181,10 @@ snapshots:
dependencies:
get-func-name: 2.0.2
+ chevrotain@7.1.1:
+ dependencies:
+ regexp-to-ast: 0.5.0
+
chokidar@3.6.0:
dependencies:
anymatch: 3.1.3
@@ -4768,7 +4798,7 @@ snapshots:
dependencies:
debug: 4.3.4
eslint: 9.2.0
- importx: 0.2.0
+ importx: 0.2.2
transitivePeerDependencies:
- supports-color
@@ -5094,7 +5124,7 @@ snapshots:
parent-module: 1.0.1
resolve-from: 4.0.0
- importx@0.2.0:
+ importx@0.2.2:
dependencies:
bundle-require: 4.1.0(esbuild@0.20.2)
debug: 4.3.4
@@ -5702,6 +5732,8 @@ snapshots:
'@eslint-community/regexpp': 4.10.0
refa: 0.12.1
+ regexp-to-ast@0.5.0: {}
+
regexp-tree@0.1.27: {}
regjsparser@0.10.0:
diff --git a/src/cli/constants.ts b/src/cli/constants.ts
index c7ed4c2fd7..9275d0321f 100644
--- a/src/cli/constants.ts
+++ b/src/cli/constants.ts
@@ -46,6 +46,7 @@ export const vscodeSettingsString = `
"jsonc",
"yaml",
"toml",
+ "xml",
"gql",
"graphql",
"astro"
diff --git a/src/configs/formatters.ts b/src/configs/formatters.ts
index 1592801e8d..e0f3782934 100644
--- a/src/configs/formatters.ts
+++ b/src/configs/formatters.ts
@@ -1,5 +1,5 @@
import { isPackageExists } from 'local-pkg'
-import { GLOB_ASTRO, GLOB_CSS, GLOB_GRAPHQL, GLOB_HTML, GLOB_LESS, GLOB_MARKDOWN, GLOB_POSTCSS, GLOB_SCSS } from '../globs'
+import { GLOB_ASTRO, GLOB_CSS, GLOB_GRAPHQL, GLOB_HTML, GLOB_LESS, GLOB_MARKDOWN, GLOB_POSTCSS, GLOB_SCSS, GLOB_XML } from '../globs'
import type { VendoredPrettierOptions } from '../vender/prettier-types'
import { ensurePackages, interopDefault, parserPlain } from '../utils'
import type { OptionsFormatters, StylisticConfig, TypedFlatConfigItem } from '../types'
@@ -17,6 +17,7 @@ export async function formatters(
html: true,
markdown: true,
slidev: isPackageExists('@slidev/cli'),
+ xml: isPackageExists('@prettier/plugin-xml'),
}
}
@@ -24,6 +25,7 @@ export async function formatters(
'eslint-plugin-format',
options.markdown && options.slidev ? 'prettier-plugin-slidev' : undefined,
options.astro ? 'prettier-plugin-astro' : undefined,
+ options.xml ? '@prettier/plugin-xml' : undefined,
])
if (options.slidev && options.markdown !== true && options.markdown !== 'prettier')
@@ -50,6 +52,13 @@ export async function formatters(
options.prettierOptions || {},
)
+ const prettierXmlOptions = {
+ xmlQuoteAttributes: 'double',
+ xmlSelfClosingSpace: true,
+ xmlSortAttributesByKey: false,
+ xmlWhitespaceSensitivity: 'ignore',
+ }
+
const dprintOptions = Object.assign(
{
indentWidth: typeof indent === 'number' ? indent : 2,
@@ -142,6 +151,29 @@ export async function formatters(
})
}
+ if (options.xml) {
+ configs.push({
+ files: [GLOB_XML],
+ languageOptions: {
+ parser: parserPlain,
+ },
+ name: 'antfu/formatter/xml',
+ rules: {
+ 'format/prettier': [
+ 'error',
+ {
+ ...prettierXmlOptions,
+ ...prettierOptions,
+ parser: 'xml',
+ plugins: [
+ '@prettier/plugin-xml',
+ ],
+ },
+ ],
+ },
+ })
+ }
+
if (options.markdown) {
const formater = options.markdown === true
? 'prettier'
diff --git a/src/globs.ts b/src/globs.ts
index 44d950adfb..a423c50640 100644
--- a/src/globs.ts
+++ b/src/globs.ts
@@ -23,6 +23,7 @@ export const GLOB_SVELTE = '**/*.svelte'
export const GLOB_VUE = '**/*.vue'
export const GLOB_YAML = '**/*.y?(a)ml'
export const GLOB_TOML = '**/*.toml'
+export const GLOB_XML = '**/*.xml'
export const GLOB_HTML = '**/*.htm?(l)'
export const GLOB_ASTRO = '**/*.astro'
export const GLOB_GRAPHQL = '**/*.{g,graph}ql'
@@ -46,6 +47,7 @@ export const GLOB_ALL_SRC = [
GLOB_SVELTE,
GLOB_VUE,
GLOB_YAML,
+ GLOB_XML,
GLOB_HTML,
]
diff --git a/src/types.ts b/src/types.ts
index 943e970928..7668a73e82 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -65,6 +65,13 @@ export interface OptionsFormatters {
*/
html?: 'prettier' | boolean
+ /**
+ * Enable formatting support for XML.
+ *
+ * Currently only support Prettier.
+ */
+ xml?: 'prettier' | boolean
+
/**
* Enable formatting support for Markdown.
*
diff --git a/src/vender/prettier-types.ts b/src/vender/prettier-types.ts
index e1fa234ecb..c9c18cd159 100644
--- a/src/vender/prettier-types.ts
+++ b/src/vender/prettier-types.ts
@@ -39,7 +39,7 @@ export interface VendoredPrettierOptionsRequired {
*/
bracketSpacing: boolean
/**
- * Put the `>` of a multi-line HTML (HTML, JSX, Vue, Angular) element at the end of the last line instead of being
+ * Put the `>` of a multi-line HTML (HTML, XML, JSX, Vue, Angular) element at the end of the last line instead of being
* alone on the next line (does not apply to self closing elements).
*/
bracketSameLine: boolean
@@ -93,10 +93,31 @@ export interface VendoredPrettierOptionsRequired {
*/
vueIndentScriptAndStyle: boolean
/**
- * Enforce single attribute per line in HTML, Vue and JSX.
+ * Enforce single attribute per line in HTML, XML, Vue and JSX.
* @default false
*/
singleAttributePerLine: boolean
+
+ /**
+ * How to handle whitespaces in XML.
+ * @default "preserve"
+ */
+ xmlQuoteAttributes: 'single' | 'double' | 'preserve'
+ /**
+ * Whether to put a space inside the brackets of self-closing XML elements.
+ * @default true
+ */
+ xmlSelfClosingSpace: boolean
+ /**
+ * Whether to sort attributes by key in XML elements.
+ * @default false
+ */
+ xmlSortAttributesByKey: boolean
+ /**
+ * How to handle whitespaces in XML.
+ * @default "ignore"
+ */
+ xmlWhitespaceSensitivity: 'ignore' | 'strict' | 'preserve'
}
export type BuiltInParserName =
@@ -122,6 +143,7 @@ export type BuiltInParserName =
| 'scss'
| 'typescript'
| 'vue'
+ | 'xml'
| 'yaml'
// This utility is here to handle the case where you have an explicit union