Repository: prettier/plugin-xml
Pull request #102 (2020-07-20 23:31:06)

Support ignore ranges


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bcfe212..a0e15d3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,20 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
 
 ## [Unreleased]
 
+### Added
+
+- Ignored print ranges using the special `<!-- prettier-ignore-start -->` and `<!-- prettier-ignore-end -->` comments. For example, you can now do:
+
+```xml
+<foo>
+  <!-- prettier-ignore-start -->
+    < this-content-will-not-be-formatted />
+  <!-- prettier-ignore-end -->
+</foo>
+```
+
+and it will maintain your formatting.
+
 ## [0.8.0] - 2020-07-03
 
 ### Added
diff --git a/README.md b/README.md
index 50a7935..3356e0c 100644
--- a/README.md
+++ b/README.md
@@ -68,6 +68,18 @@ Or, they can be passed to `prettier` as arguments:
 prettier --tab-width 4 --write '**/*.xml'
 ```
 
+### Ignore ranges
+
+You can use two special comments to get prettier to ignore formatting a specific piece of the document, as in the following example:
+
+```xml
+<foo>
+  <!-- prettier-ignore-start -->
+    < this-content-will-not-be-formatted />
+  <!-- prettier-ignore-end -->
+</foo>
+```
+
 ## Contributing
 
 Bug reports and pull requests are welcome on GitHub at https://github.com/prettier/plugin-xml.
diff --git a/src/print.js b/src/print.js
index a7fc4cd..d797e4f 100644
--- a/src/print.js
+++ b/src/print.js
@@ -9,14 +9,37 @@ const {
   softline
 } = require("prettier/doc").builders;
 
+const ignoreStartComment = "<!-- prettier-ignore-start -->";
+const ignoreEndComment = "<!-- prettier-ignore-end -->";
+
+const hasIgnoreRanges = (comments) => {
+  if (!comments || comments.length === 0) {
+    return;
+  }
+
+  comments.sort((left, right) => left.offset - right.offset);
+
+  let startFound = false;
+  for (let idx = 0; idx < comments.length; idx += 1) {
+    if (comments[idx].image === ignoreStartComment) {
+      startFound = true;
+    } else if (startFound && comments[idx].image === ignoreEndComment) {
+      return true;
+    }
+  }
+
+  return false;
+};
+
 const elementOnly = (node) => {
-  const { CData, chardata, element, reference } = node.children;
+  const { CData, Comment, chardata, element, reference } = node.children;
 
   return (
     !CData &&
     (!chardata || chardata.every((datum) => !datum.children.TEXT)) &&
     element &&
-    !reference
+    !reference &&
+    !hasIgnoreRanges(Comment)
   );
 };
 
@@ -50,6 +73,13 @@ const printReference = (node) => ({
   printed: (node.children.CharRef || node.children.EntityRef)[0].image
 });
 
+const replaceNewlinesWithLiteralLines = (content) =>
+  concat(
+    content
+      .split(/(\n)/g)
+      .map((value, idx) => (idx % 2 === 0 ? value : literalline))
+  );
+
 const nodes = {
   attribute: (path, _opts, _print) => {
     const { Name, EQUALS, STRING } = path.getValue().children;
@@ -76,19 +106,57 @@ const nodes = {
       reference = []
     } = path.getValue().children;
 
-    return group(
-      concat(
-        []
-          .concat(CData.map(printCData))
-          .concat(Comment.map(printComment))
-          .concat(chardata.map(printCharData(path, print)))
-          .concat(element.map(printElement(path, print)))
-          .concat(PROCESSING_INSTRUCTION.map(printProcessingInstruction))
-          .concat(reference.map(printReference))
-          .sort((left, right) => left.offset - right.offset)
-          .map(({ printed }) => printed)
-      )
-    );
+    let children = []
+      .concat(CData.map(printCData))
+      .concat(Comment.map(printComment))
+      .concat(chardata.map(printCharData(path, print)))
+      .concat(element.map(printElement(path, print)))
+      .concat(PROCESSING_INSTRUCTION.map(printProcessingInstruction))
+      .concat(reference.map(printReference));
+
+    if (hasIgnoreRanges(Comment)) {
+      Comment.sort((left, right) => left.offset - right.offset);
+
+      const ignoreRanges = [];
+      let ignoreStart = null;
+
+      // Build up a list of ignored ranges from the original text based on the
+      // special prettier-ignore-* comments
+      Comment.forEach((comment) => {
+        if (comment.image === ignoreStartComment) {
+          ignoreStart = comment;
+        } else if (ignoreStart && comment.image === ignoreEndComment) {
+          ignoreRanges.push({
+            start: ignoreStart.startOffset,
+            end: comment.endOffset
+          });
+
+          ignoreStart = null;
+        }
+      });
+
+      // Filter the printed children to only include the ones that are outside
+      // of each of the ignored ranges
+      children = children.filter((child) =>
+        ignoreRanges.every(
+          ({ start, end }) => child.offset < start || child.offset > end
+        )
+      );
+
+      // Push each of the ignored ranges into the child list as its own element
+      // so that the original content is still included
+      ignoreRanges.forEach(({ start, end }) => {
+        const content = opts.originalText.slice(start, end + 1);
+
+        children.push({
+          offset: start,
+          printed: replaceNewlinesWithLiteralLines(content)
+        });
+      });
+    }
+
+    children.sort((left, right) => left.offset - right.offset);
+    return group(concat(children.map(({ printed }) => printed)));
   },
   docTypeDecl: (path, opts, print) => {
     const { DocType, Name, externalID, CLOSE } = path.getValue().children;


Test patch:
diff --git a/test/__snapshots__/format.test.js.snap b/test/__snapshots__/format.test.js.snap
index e688005..501c6c5 100644
--- a/test/__snapshots__/format.test.js.snap
+++ b/test/__snapshots__/format.test.js.snap
@@ -34,6 +34,11 @@ exports[`defaults 1`] = `
         <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>
 </svg>
 <!-- bar -->
 "
@@ -73,6 +78,11 @@ exports[`xmlWhitespaceSensitivity => ignore 1`] = `
     <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>
 </svg>
 <!-- bar -->
 "
diff --git a/test/fixture.xml b/test/fixture.xml
index 27b4846..52911cf 100644
--- a/test/fixture.xml
+++ b/test/fixture.xml
@@ -31,5 +31,10 @@
         <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>
 </svg>
 <!-- bar -->