Skip to content

Commit

Permalink
Merge pull request #685 from bweis/sorting
Browse files Browse the repository at this point in the history
 Added feature to order XML attributes by key alphabetically while prioritizing xmlns attributes
  • Loading branch information
kddnewton authored Jul 7, 2023
2 parents 87fb858 + a9f7cb4 commit 645fdbc
Show file tree
Hide file tree
Showing 8 changed files with 434 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a

## [Unreleased]

- the `xmlSortAttributesByKey: true | false` option has been added. See the README.
- The `xmlQuoteAttributes: "preserve" | "single" | "double"` option has been added. See the README.

## [3.0.0] - 2023-07-06
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,14 @@ The `prettier` executable is now installed and ready for use:
Below are the options (from [`src/plugin.js`](src/plugin.js)) that `@prettier/plugin-xml` currently supports:

| API Option | CLI Option | Default | Description |
| -------------------------- | ------------------------------ | :----------: | ------------------------------------------------------------------------------------------------------------------------ |
| -------------------------- | ------------------------------ | :----------: | ------------------------------------------------------------------------------------------------------------------------ | --- |
| `bracketSameLine` | `--bracket-same-line` | `true` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#bracket-same-line)) |
| `printWidth` | `--print-width` | `80` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#print-width)). |
| `singleAttributePerLine` | `--single-attribute-per-line` | `false` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#single-attribute-per-line)) |
| `singleAttributePerLine` | `--single-attribute-per-line` | `false` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#single-attribute-per-line)) | |
| `tabWidth` | `--tab-width` | `2` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#tab-width)). |
| `xmlQuoteAttributes` | `--xml-quote-attributes` | `"preserve"` | Options are `"preserve"`, `"single"`, and `"double"` |
| `xmlSelfClosingSpace` | `--xml-self-closing-space` | `true` | Adds a space before self-closing tags. |
| `xmlSortAttributesByKey` | `--xml-sort-attributes-by-key` | `false` | Orders XML attributes by key alphabetically while prioritizing xmlns attributes. |
| `xmlWhitespaceSensitivity` | `--xml-whitespace-sensitivity` | `"strict"` | Options are `"strict"`, `"preserve"`, and `"ignore"`. You may want `"ignore"` or `"preserve"`, [see below](#whitespace). |

Any of these can be added to your existing [prettier configuration
Expand Down
7 changes: 7 additions & 0 deletions src/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ const plugin = {
],
since: "0.6.0"
},
xmlSortAttributesByKey: {
type: "boolean",
category: "XML",
default: false,
description:
"Orders XML attributes by key alphabetically while prioritizing xmlns attributes."
},
xmlQuoteAttributes: {
type: "choice",
category: "XML",
Expand Down
22 changes: 21 additions & 1 deletion src/printer.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as doc from "prettier/doc";
import embed from "./embed.js";
import sortAttributesByKey from "./util/sortAttributesByKey.js";

const { fill, group, hardline, indent, join, line, literalline, softline } =
doc.builders;
Expand Down Expand Up @@ -369,9 +370,28 @@ function printElement(path, opts, print) {
const parts = [OPEN[0].image, Name[0].image];

if (attribute) {
const attributes = path.map(
(attributePath) => ({
node: attributePath.getValue(),
printed: print(attributePath)
}),
"children",
"attribute"
);

if (opts.xmlSortAttributesByKey) {
sortAttributesByKey(attributes);
}

const separator = opts.singleAttributePerLine ? hardline : line;
parts.push(
indent([line, join(separator, path.map(print, "children", "attribute"))])
indent([
line,
join(
separator,
attributes.map(({ printed }) => printed)
)
])
);
}

Expand Down
98 changes: 98 additions & 0 deletions src/util/sortAttributesByKey.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/**
* An attribute object.
* @typedef {Object} Attribute
* @property {Object} node - Indicates the value of the attribute returned from prettier/doc.
* @property {String[]} printed - Indicates the printed format of the attribute.
*/

/**
*
* @param {Array} attributes
* @returns {Array} a copy of attributes that has been sorted
*
* <p> This is complicated because we need to parse the xmlns out of the
* attribute keys, prioritize xmlns attributes first, and then order
* accordingly. This is because I would like to extend this in the future to
* prioritize certain keys within certain namespaces.
*/
function sortAttributes(attributes) {
attributes.sort((leftAttr, rightAttr) => {
const leftToken = leftAttr.node.children.Name[0].image;
const rightToken = rightAttr.node.children.Name[0].image;
// Handle the highest priority attribute
if (isXmlns(leftToken)) {
return -1; // param1 appears earlier when sorted
} else if (isXmlns(rightToken)) {
return 1; // param1 appears later when sorted
}

if (containsNamespace(leftToken) && containsNamespace(rightToken)) {
return compareTwoNamespacedAttributeKeys(leftToken, rightToken);
}
// Handle the 1 but not both containing a namespace
if (containsNamespace(leftToken) && !containsNamespace(rightToken)) {
return -1; // key1 appears earlier when sorted
} else if (!containsNamespace(leftToken) && containsNamespace(rightToken)) {
return 1; // key1 appears later when sorted
}
return standardComparison(leftToken, rightToken);
});
}

/**
*
* @param {String} leftKey
* @param {String} rightKey
* @returns {Number} a comparison value
*/
function compareTwoNamespacedAttributeKeys(leftKey, rightKey) {
const [ns1, k1] = leftKey.split(":");
const [ns2, k2] = rightKey.split(":");
// if namespaces are equal, simply compare keys
if (ns1 === ns2) {
return standardComparison(k1, k2);
}
// Handle the 1 but not both being an xmlns
if (isXmlns(ns1) && !isXmlns(ns2)) {
return -1; // key1 appears earlier when sorted
} else if (!isXmlns(ns1) && isXmlns(ns2)) {
return 1; // key1 appears later when sorted
}
return standardComparison(ns1, ns2);
}

/**
*
* @param {String} key
* @returns {Boolean} whether or not that key is namespace-d
*/
function containsNamespace(key) {
return key.includes(":");
}

/**
*
* @param {String} value
* @returns {Boolean} whether or not that value is xmlns
*/
function isXmlns(value) {
return value === "xmlns";
}

/**
*
* @param {String} key1
* @param {String} key2
* @returns {Number} a comparison value
*/
function standardComparison(key1, key2) {
if (key1 < key2) {
return -1; // param1 appears earlier when sorted
} else if (key1 > key2) {
return 1; // param1 appears later when sorted
} else {
return 0;
}
}

export default sortAttributes;
Loading

0 comments on commit 645fdbc

Please sign in to comment.