Skip to content

Commit debd320

Browse files
committed
fix: implement xmlWhitespaceSensitivity: 'preserve'
Fixes prettier#478
1 parent 2bc6e1b commit debd320

File tree

7 files changed

+216
-35
lines changed

7 files changed

+216
-35
lines changed

.github/ISSUE_TEMPLATE/formatting.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ body:
2424
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!
2525
options:
2626
- "strict"
27-
- "ignore"
27+
- "preserve"
28+
- "ignore"
2829
validations:
2930
required: true
3031
- type: dropdown

README.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ Below are the options (from [`src/plugin.js`](src/plugin.js)) that `@prettier/pl
5353
| `singleAttributePerLine` | `--single-attribute-per-line` | `false` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#single-attribute-per-line)) |
5454
| `tabWidth` | `--tab-width` | `2` | Same as in Prettier ([see prettier docs](https://prettier.io/docs/en/options.html#tab-width)). |
5555
| `xmlSelfClosingSpace` | `--xml-self-closing-space` | `true` | Adds a space before self-closing tags. |
56-
| `xmlWhitespaceSensitivity` | `--xml-whitespace-sensitivity` | `"strict"` | Options are `"strict"` and `"ignore"`. You may want `"ignore"`, [see below](#whitespace). |
56+
| `xmlWhitespaceSensitivity` | `--xml-whitespace-sensitivity` | `"strict"` | Options are `"strict"``"preserve"` and `"ignore"`. You may want `"preserve"`, [see below](#whitespace). |
5757

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

7575
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.
7676

77-
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.
77+
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.
78+
79+
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).
7880

7981
### Ignore ranges
8082

src/plugin.js

+4
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ const plugin = {
2828
value: "strict",
2929
description: "Whitespaces are considered sensitive in all elements."
3030
},
31+
{
32+
value: "preserve",
33+
description: "Whitespaces within text nodes in XML elements and attributes are considered sensitive."
34+
},
3135
{
3236
value: "ignore",
3337
description: "Whitespaces are considered insensitive in all elements."

src/printer.js

+81-32
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ const printer = {
282282
]);
283283

284284
if (
285-
opts.xmlWhitespaceSensitivity === "ignore" &&
285+
opts.xmlWhitespaceSensitivity !== "strict" &&
286286
isWhitespaceIgnorable(content[0])
287287
) {
288288
const fragments = path.call(
@@ -297,37 +297,72 @@ const printer = {
297297
}
298298

299299
if (children.chardata) {
300-
childrenPath.each((charDataPath) => {
301-
const chardata = charDataPath.getValue();
302-
if (!chardata.children.TEXT) {
303-
return;
304-
}
305-
306-
const content = chardata.children.TEXT[0].image.trim();
307-
const printed = group(
308-
content.split(/(\n)/g).map((value) => {
309-
if (value === "\n") {
310-
return literalline;
311-
}
312-
313-
return fill(
314-
value
315-
.split(/\b( +)\b/g)
316-
.map((segment, index) =>
317-
index % 2 === 0 ? segment : line
318-
)
319-
);
320-
})
321-
);
322-
323-
const location = chardata.location;
324-
response.push({
325-
offset: location.startOffset,
326-
startLine: location.startLine,
327-
endLine: location.endLine,
328-
printed
329-
});
330-
}, "chardata");
300+
// Does this chardata node have any non-whitespace text?
301+
const containsText = children.chardata.some(({children}) => !!children.TEXT);
302+
303+
if (containsText && opts.xmlWhitespaceSensitivity === "preserve") {
304+
let prevLocation;
305+
childrenPath.each((charDataPath) => {
306+
const chardata = charDataPath.getValue();
307+
const location = chardata.location;
308+
const content = print(charDataPath);
309+
310+
if (prevLocation &&
311+
location.startColumn &&
312+
prevLocation.endColumn &&
313+
location.startLine === prevLocation.endLine &&
314+
location.startColumn === prevLocation.endColumn + 1) {
315+
// continuation of previous fragment
316+
const prevFragment = response[response.length - 1];
317+
prevFragment.endLine = location.endLine;
318+
prevFragment.printed = group([
319+
prevFragment.printed,
320+
content
321+
]);
322+
} else {
323+
response.push({
324+
offset: location.startOffset,
325+
startLine: location.startLine,
326+
endLine: location.endLine,
327+
printed: content,
328+
hasSignificantWhitespace: true,
329+
})
330+
}
331+
prevLocation = location;
332+
}, 'chardata');
333+
} else {
334+
childrenPath.each((charDataPath) => {
335+
const chardata = charDataPath.getValue();
336+
if (!chardata.children.TEXT) {
337+
return;
338+
}
339+
340+
const content = chardata.children.TEXT[0].image.trim();
341+
const printed = group(
342+
content.split(/(\n)/g).map((value) => {
343+
if (value === "\n") {
344+
return literalline;
345+
}
346+
347+
return fill(
348+
value
349+
.split(/\b( +)\b/g)
350+
.map((segment, index) =>
351+
index % 2 === 0 ? segment : line
352+
)
353+
);
354+
})
355+
);
356+
357+
const location = chardata.location;
358+
response.push({
359+
offset: location.startOffset,
360+
startLine: location.startLine,
361+
endLine: location.endLine,
362+
printed
363+
});
364+
}, "chardata");
365+
}
331366
}
332367

333368
if (children.element) {
@@ -361,6 +396,20 @@ const printer = {
361396

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

399+
if (opts.xmlWhitespaceSensitivity === "preserve") {
400+
const hasSignificantWhitespace = fragments.some(
401+
({ hasSignificantWhitespace }) => !!hasSignificantWhitespace
402+
);
403+
if (hasSignificantWhitespace) {
404+
// Return fragments unaltered
405+
return group([
406+
openTag,
407+
fragments.map(({ printed }) => printed),
408+
closeTag
409+
])
410+
}
411+
}
412+
364413
// If the only content of this tag is chardata, then use a softline so
365414
// that we won't necessarily break (to allow <foo>bar</foo>).
366415
if (

test/__snapshots__/format.test.js.snap

+115
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,13 @@ use {
6868
</p>
6969
<span>content</span>
7070
71+
<div>
72+
text with space
73+
<div>
74+
<hr />
75+
</div>
76+
</div>
77+
7178
<div>
7279
even more
7380
<content />
@@ -145,6 +152,13 @@ use {
145152
</p>
146153
<span>content</span>
147154
155+
<div>
156+
text with space
157+
<div>
158+
<hr/>
159+
</div>
160+
</div>
161+
148162
<div>
149163
even more
150164
<content/>
@@ -220,6 +234,7 @@ use {
220234
content
221235
</span>
222236
237+
<div> text with space<div><hr /></div> </div>
223238
224239
<div>
225240
even more
@@ -312,6 +327,13 @@ use {
312327
</p>
313328
<span>content</span>
314329
330+
<div>
331+
text with space
332+
<div>
333+
<hr />
334+
</div>
335+
</div>
336+
315337
<div>
316338
even more
317339
<content />
@@ -391,6 +413,13 @@ use {
391413
</p>
392414
<span>content</span>
393415
416+
<div>
417+
text with space
418+
<div>
419+
<hr/>
420+
</div>
421+
</div>
422+
394423
<div>
395424
even more
396425
<content/>
@@ -470,6 +499,92 @@ use {
470499
</p>
471500
<span>content</span>
472501
502+
<div>
503+
text with space
504+
<div>
505+
<hr />
506+
</div>
507+
</div>
508+
509+
<div>
510+
even more
511+
<content />
512+
</div>
513+
</svg>
514+
<!-- bar -->
515+
"
516+
`;
517+
518+
exports[`xmlWhitespaceSensitivity => preserve 1`] = `
519+
"<?xml version="1.0" encoding="UTF-8" ?>
520+
<!DOCTYPE module PUBLIC "-//Puppy Crawl//DTD Check Configuration 1.3//EN"
521+
"https://www.puppycrawl.com/dtds/configuration_1_3.dtd">
522+
<?xml-model href="project.rnc" type="application/relax-ng-compact-syntax"?>
523+
<!-- foo -->
524+
<svg
525+
xmlns="http://www.w3.org/2000/svg"
526+
xmlns:xlink="http://www.w3.org/1999/xlink"
527+
width="200"
528+
height="100"
529+
viewBox="0 0 200 100"
530+
>
531+
<title>Style inheritance and the use element</title>
532+
<desc _attr="attr">
533+
&anp; &#12345;
534+
<![CDATA[
535+
foo
536+
]]>
537+
bar
538+
</desc>
539+
<?pagebreak?>
540+
541+
<style />
542+
<style />
543+
<style type="text/css">
544+
circle {
545+
stroke-opacity: 0.7;
546+
}
547+
.special circle {
548+
stroke: green;
549+
}
550+
use {
551+
stroke: purple;
552+
fill: orange;
553+
}
554+
</style>
555+
<script value="lint" />
556+
557+
<yaml
558+
myveryveryveryverylongattributename="myveryveryveryverylongattributevalue"
559+
>
560+
- 1
561+
- 2
562+
- 3
563+
</yaml>
564+
565+
<!-- inner comment -->
566+
567+
<?pagebreak?>
568+
<g class="special" style="fill: blue">
569+
<circle id="c" cy="50" cx="50" r="40" stroke-width="20" />
570+
</g>
571+
<use xlink:href="#c" x="100" />
572+
<ignored>
573+
<!-- prettier-ignore-start -->
574+
< ignored />
575+
<!-- prettier-ignore-end -->
576+
</ignored>
577+
<p>
578+
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.
579+
</p>
580+
<span>
581+
content
582+
</span>
583+
584+
<div> text with space<div>
585+
<hr />
586+
</div> </div>
587+
473588
<div>
474589
even more
475590
<content />

test/fixture.xml

+1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
content
5656
</span>
5757

58+
<div> text with space<div><hr/></div> </div>
5859

5960
<div>
6061
even more

test/format.test.js

+9
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,12 @@ test("singleAttributePerLine => true", async () => {
6363

6464
expect(formatted).toMatchSnapshot();
6565
});
66+
67+
test("xmlWhitespaceSensitivity => preserve", async () => {
68+
const formatted = await format(fixture, {
69+
xmlWhitespaceSensitivity: "preserve"
70+
});
71+
console.log({formatted});
72+
73+
expect(formatted).toMatchSnapshot();
74+
});

0 commit comments

Comments
 (0)