Skip to content

Commit

Permalink
Merge pull request #635 from kendrickw/feature/issue478
Browse files Browse the repository at this point in the history
feat: implement xmlWhitespaceSensitivity: 'preserve'
  • Loading branch information
kddnewton authored Dec 13, 2022
2 parents 2bc6e1b + debd320 commit 53b3739
Show file tree
Hide file tree
Showing 7 changed files with 216 additions and 35 deletions.
3 changes: 2 additions & 1 deletion .github/ISSUE_TEMPLATE/formatting.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ body:
description: What value do you have the `xmlWhitespaceSensitivity` option set to? (Defaults to `"strict"`.) Be 100% sure changing this to `"ignore"` doesn't fix your issue!
options:
- "strict"
- "ignore"
- "preserve"
- "ignore"
validations:
required: true
- type: dropdown
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Below are the options (from [`src/plugin.js`](src/plugin.js)) that `@prettier/pl
| `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)). |
| `xmlSelfClosingSpace` | `--xml-self-closing-space` | `true` | Adds a space before self-closing tags. |
| `xmlWhitespaceSensitivity` | `--xml-whitespace-sensitivity` | `"strict"` | Options are `"strict"` and `"ignore"`. You may want `"ignore"`, [see below](#whitespace). |
| `xmlWhitespaceSensitivity` | `--xml-whitespace-sensitivity` | `"strict"` | Options are `"strict"``"preserve"` and `"ignore"`. You may want `"preserve"`, [see below](#whitespace). |

Any of these can be added to your existing [prettier configuration
file](https://prettier.io/docs/en/configuration.html). For example:
Expand All @@ -74,7 +74,9 @@ prettier --tab-width 4 --write '**/*.xml'

In XML, by default, all whitespace inside elements has semantic meaning. For prettier to maintain its contract of not changing the semantic meaning of your program, this means the default for `xmlWhitespaceSensitivity` is `"strict"`. When running in this mode, prettier's ability to rearrange your markup is somewhat limited, as it has to maintain the exact amount of whitespace that you input within elements.

If you're sure that the XML files that you're formatting do not require whitespace sensitivity, you can use the `"ignore"` option, as this will produce a standardized amount of whitespace. This will fix any indentation issues, and collapse excess blank lines (max of 1 blank line). For most folks most of the time, this is probably the option that you want.
You can use the `"preserve"` option, if you want to preserve the whitespaces of the text node within XML elements and attributes. (see [Issue #478](https://github.com/prettier/plugin-xml/issues/478)). For most folks most of the time, this is probably the option that you want.

If you're sure that the XML files that you're formatting do not require whitespace sensitivity, you can use the `"ignore"` option, as this will produce a standardized amount of whitespace. This will fix any indentation issues, and collapse excess blank lines (max of 1 blank line).

### Ignore ranges

Expand Down
4 changes: 4 additions & 0 deletions src/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ const plugin = {
value: "strict",
description: "Whitespaces are considered sensitive in all elements."
},
{
value: "preserve",
description: "Whitespaces within text nodes in XML elements and attributes are considered sensitive."
},
{
value: "ignore",
description: "Whitespaces are considered insensitive in all elements."
Expand Down
113 changes: 81 additions & 32 deletions src/printer.js
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ const printer = {
]);

if (
opts.xmlWhitespaceSensitivity === "ignore" &&
opts.xmlWhitespaceSensitivity !== "strict" &&
isWhitespaceIgnorable(content[0])
) {
const fragments = path.call(
Expand All @@ -297,37 +297,72 @@ const printer = {
}

if (children.chardata) {
childrenPath.each((charDataPath) => {
const chardata = charDataPath.getValue();
if (!chardata.children.TEXT) {
return;
}

const content = chardata.children.TEXT[0].image.trim();
const printed = group(
content.split(/(\n)/g).map((value) => {
if (value === "\n") {
return literalline;
}

return fill(
value
.split(/\b( +)\b/g)
.map((segment, index) =>
index % 2 === 0 ? segment : line
)
);
})
);

const location = chardata.location;
response.push({
offset: location.startOffset,
startLine: location.startLine,
endLine: location.endLine,
printed
});
}, "chardata");
// Does this chardata node have any non-whitespace text?
const containsText = children.chardata.some(({children}) => !!children.TEXT);

if (containsText && opts.xmlWhitespaceSensitivity === "preserve") {
let prevLocation;
childrenPath.each((charDataPath) => {
const chardata = charDataPath.getValue();
const location = chardata.location;
const content = print(charDataPath);

if (prevLocation &&
location.startColumn &&
prevLocation.endColumn &&
location.startLine === prevLocation.endLine &&
location.startColumn === prevLocation.endColumn + 1) {
// continuation of previous fragment
const prevFragment = response[response.length - 1];
prevFragment.endLine = location.endLine;
prevFragment.printed = group([
prevFragment.printed,
content
]);
} else {
response.push({
offset: location.startOffset,
startLine: location.startLine,
endLine: location.endLine,
printed: content,
hasSignificantWhitespace: true,
})
}
prevLocation = location;
}, 'chardata');
} else {
childrenPath.each((charDataPath) => {
const chardata = charDataPath.getValue();
if (!chardata.children.TEXT) {
return;
}

const content = chardata.children.TEXT[0].image.trim();
const printed = group(
content.split(/(\n)/g).map((value) => {
if (value === "\n") {
return literalline;
}

return fill(
value
.split(/\b( +)\b/g)
.map((segment, index) =>
index % 2 === 0 ? segment : line
)
);
})
);

const location = chardata.location;
response.push({
offset: location.startOffset,
startLine: location.startLine,
endLine: location.endLine,
printed
});
}, "chardata");
}
}

if (children.element) {
Expand Down Expand Up @@ -361,6 +396,20 @@ const printer = {

fragments.sort((left, right) => left.offset - right.offset);

if (opts.xmlWhitespaceSensitivity === "preserve") {
const hasSignificantWhitespace = fragments.some(
({ hasSignificantWhitespace }) => !!hasSignificantWhitespace
);
if (hasSignificantWhitespace) {
// Return fragments unaltered
return group([
openTag,
fragments.map(({ printed }) => printed),
closeTag
])
}
}

// If the only content of this tag is chardata, then use a softline so
// that we won't necessarily break (to allow <foo>bar</foo>).
if (
Expand Down
115 changes: 115 additions & 0 deletions test/__snapshots__/format.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ use {
</p>
<span>content</span>
<div>
text with space
<div>
<hr />
</div>
</div>
<div>
even more
<content />
Expand Down Expand Up @@ -145,6 +152,13 @@ use {
</p>
<span>content</span>
<div>
text with space
<div>
<hr/>
</div>
</div>
<div>
even more
<content/>
Expand Down Expand Up @@ -220,6 +234,7 @@ use {
content
</span>
<div> text with space<div><hr /></div> </div>
<div>
even more
Expand Down Expand Up @@ -312,6 +327,13 @@ use {
</p>
<span>content</span>
<div>
text with space
<div>
<hr />
</div>
</div>
<div>
even more
<content />
Expand Down Expand Up @@ -391,6 +413,13 @@ use {
</p>
<span>content</span>
<div>
text with space
<div>
<hr/>
</div>
</div>
<div>
even more
<content/>
Expand Down Expand Up @@ -470,6 +499,92 @@ use {
</p>
<span>content</span>
<div>
text with space
<div>
<hr />
</div>
</div>
<div>
even more
<content />
</div>
</svg>
<!-- bar -->
"
`;

exports[`xmlWhitespaceSensitivity => preserve 1`] = `
"<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE module PUBLIC "-//Puppy Crawl//DTD Check Configuration 1.3//EN"
"https://www.puppycrawl.com/dtds/configuration_1_3.dtd">
<?xml-model href="project.rnc" type="application/relax-ng-compact-syntax"?>
<!-- foo -->
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="200"
height="100"
viewBox="0 0 200 100"
>
<title>Style inheritance and the use element</title>
<desc _attr="attr">
&anp; &#12345;
<![CDATA[
foo
]]>
bar
</desc>
<?pagebreak?>
<style />
<style />
<style type="text/css">
circle {
stroke-opacity: 0.7;
}
.special circle {
stroke: green;
}
use {
stroke: purple;
fill: orange;
}
</style>
<script value="lint" />
<yaml
myveryveryveryverylongattributename="myveryveryveryverylongattributevalue"
>
- 1
- 2
- 3
</yaml>
<!-- inner comment -->
<?pagebreak?>
<g class="special" style="fill: blue">
<circle id="c" cy="50" cx="50" r="40" stroke-width="20" />
</g>
<use xlink:href="#c" x="100" />
<ignored>
<!-- prettier-ignore-start -->
< ignored />
<!-- prettier-ignore-end -->
</ignored>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed at est eget enim consectetur accumsan. Aliquam pretium sodales ipsum quis dignissim. Sed id sem vel diam luctus fringilla. Aliquam quis egestas magna. Curabitur molestie lorem et odio porta, et molestie libero laoreet. Morbi rhoncus sagittis cursus. Nullam vehicula pretium consequat. Praesent porta ante at posuere sollicitudin. Nullam commodo tempor arcu, at condimentum neque elementum ut.
</p>
<span>
content
</span>
<div> text with space<div>
<hr />
</div> </div>
<div>
even more
<content />
Expand Down
1 change: 1 addition & 0 deletions test/fixture.xml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
content
</span>

<div> text with space<div><hr/></div> </div>

<div>
even more
Expand Down
9 changes: 9 additions & 0 deletions test/format.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,12 @@ test("singleAttributePerLine => true", async () => {

expect(formatted).toMatchSnapshot();
});

test("xmlWhitespaceSensitivity => preserve", async () => {
const formatted = await format(fixture, {
xmlWhitespaceSensitivity: "preserve"
});
console.log({formatted});

expect(formatted).toMatchSnapshot();
});

0 comments on commit 53b3739

Please # to comment.