Repository: prettier/plugin-xml
Pull request #299 (2021-09-23 16:01:19)

Stricter types for development


Patch:
diff --git a/.prettierignore b/.prettierignore
index 3ac177b..188a6f2 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -6,5 +6,6 @@
 /yarn.lock
 /LICENSE
 
+/src/types.ts
 /test/fixture.xml
 /test/__snapshots__/*
diff --git a/bin/print.ts b/bin/print.ts
index 87da239..090fc1c 100755
--- a/bin/print.ts
+++ b/bin/print.ts
@@ -4,15 +4,14 @@ import fs from "fs";
 import prettier from "prettier";
 
 import plugin from "../src/plugin";
-import { XMLOptions } from "../src/types";
 
 const code = fs.existsSync(process.argv[2])
   ? fs.readFileSync(process.argv[2], "utf-8")
   : process.argv.slice(2).join(" ").replace(/\\n/g, "\n");
 
-const options: Partial<XMLOptions> = {
+const options = {
   parser: "xml",
-  plugins: [plugin],
+  plugins: [plugin as any as string],
   xmlWhitespaceSensitivity: "ignore"
 };
 
diff --git a/src/embed.ts b/src/embed.ts
index d52f9a1..9ea8b78 100644
--- a/src/embed.ts
+++ b/src/embed.ts
@@ -1,8 +1,13 @@
-import { ContentCtx, ElementCstNode } from "@xml-tools/parser";
-import type { AstPath, Doc, ParserOptions, Printer } from "prettier";
 import { builders, utils } from "prettier/doc";
-
-import { XMLAst, XMLOptions } from "./types";
+import {
+  AnyNode,
+  ContentCtx,
+  Doc,
+  ElementCstNode,
+  Embed,
+  Options,
+  Path
+} from "./types";
 
 const {
   dedentToRoot,
@@ -27,11 +32,11 @@ function replaceNewlines(doc: Doc) {
 
 // Get the start and end element tags from the current node on the tree
 function getElementTags(
-  path: AstPath<XMLAst>,
-  opts: XMLOptions,
-  print: (path: AstPath<XMLAst>) => Doc
+  path: Path<ElementCstNode>,
+  opts: Options,
+  print: (path: Path<AnyNode>) => Doc
 ) {
-  const node = path.getValue() as ElementCstNode;
+  const node = path.getValue();
   const { OPEN, Name, attribute, START_CLOSE, SLASH_OPEN, END_NAME, END } =
     node.children;
 
@@ -55,7 +60,7 @@ function getElementTags(
 
 // Get the name of the parser that is represented by the given element node,
 // return null if a matching parser cannot be found
-function getParser(node: ElementCstNode, opts: ParserOptions<XMLAst>) {
+function getParser(node: ElementCstNode, opts: Options) {
   const { Name, attribute } = node.children;
   const parser = Name[0].image.toLowerCase();
 
@@ -114,7 +119,7 @@ function getSource(content: ContentCtx) {
     .join("");
 }
 
-const embed: Printer<XMLAst>["embed"] = (path, print, textToDoc, opts) => {
+const embed: Embed = (path, print, textToDoc, opts) => {
   const node = path.getValue();
 
   // If the node isn't an element node, then skip
@@ -138,14 +143,17 @@ const embed: Printer<XMLAst>["embed"] = (path, print, textToDoc, opts) => {
 
   // Get the open and close tags of this element, then return the properly
   // formatted content enclosed within them
-  const { openTag, closeTag } = getElementTags(path, opts as XMLOptions, print);
+  const nodePath = path as Path<typeof node>;
+  const { openTag, closeTag } = getElementTags(nodePath, opts, print);
 
   return group([
     openTag,
     literalline,
     dedentToRoot(
       replaceNewlines(
-        utils.stripTrailingHardline(textToDoc(getSource(content), { parser }))
+        utils.stripTrailingHardline(
+          textToDoc(getSource(content), { ...opts, parser })
+        )
       )
     ),
     hardline,
diff --git a/src/parser.ts b/src/parser.ts
index 9d20a10..4907e08 100644
--- a/src/parser.ts
+++ b/src/parser.ts
@@ -1,5 +1,5 @@
-import type { Parser } from "prettier";
 import { parse as xmlToolsParse } from "@xml-tools/parser";
+import type { Parser } from "./types";
 
 const parser: Parser = {
   parse(text) {
@@ -13,16 +13,10 @@ const parser: Parser = {
   },
   astFormat: "xml",
   locStart(node) {
-    if (node.location) {
-      return node.location.startOffset;
-    }
-    return node.startOffset;
+    return node.location!.startOffset;
   },
   locEnd(node) {
-    if (node.location) {
-      return node.location.endOffset;
-    }
-    return node.endOffset;
+    return node.location!.endOffset!;
   }
 };
 
diff --git a/src/plugin.ts b/src/plugin.ts
index 06fd3ff..1d2e5a7 100644
--- a/src/plugin.ts
+++ b/src/plugin.ts
@@ -1,12 +1,11 @@
-import type { Plugin } from "prettier";
 import prettier from "prettier";
 
-import type { XMLAst } from "./types";
+import type { Plugin } from "./types";
 import parser from "./parser";
 import printer from "./printer";
 
 // These are the extra options defined by this plugin.
-const options: Plugin<XMLAst>["options"] = {
+const options: Plugin["options"] = {
   xmlWhitespaceSensitivity: {
     type: "choice",
     category: "XML",
@@ -44,7 +43,7 @@ if (
   };
 }
 
-const plugin: Plugin<XMLAst> = {
+const plugin: Plugin = {
   languages: [
     {
       name: "XML",
diff --git a/src/printer.ts b/src/printer.ts
index 3efa43f..c1c3849 100644
--- a/src/printer.ts
+++ b/src/printer.ts
@@ -1,15 +1,6 @@
-import type {
-  ChardataCstNode,
-  ContentCstNode,
-  ReferenceCstNode
-} from "@xml-tools/parser";
-import type { IToken } from "chevrotain";
-import type { AstPath, Doc, Printer } from "prettier";
 import { builders } from "prettier/doc";
-
 import embed from "./embed";
-
-import { ContentCstNodeExt, XMLAst, XMLOptions } from "./types";
+import type { ContentCstNode, Doc, IToken, Path, Printer } from "./types";
 
 const { fill, group, hardline, indent, join, line, literalline, softline } =
   builders;
@@ -42,13 +33,20 @@ function isWhitespaceIgnorable(node: ContentCstNode) {
   return !CData && !reference && !hasIgnoreRanges(Comment);
 }
 
-function printIToken(path: AstPath<XMLAst>) {
-  const node = path.getValue() as any as IToken;
+type Fragment = {
+  offset: number;
+  printed: Doc;
+  startLine?: number;
+  endLine?: number;
+};
+
+function printIToken(path: Path<IToken>): Fragment {
+  const node = path.getValue();
 
   return {
     offset: node.startOffset,
-    startLine: node.startLine!,
-    endLine: node.endLine!,
+    startLine: node.startLine,
+    endLine: node.endLine,
     printed: node.image
   };
 }
@@ -59,9 +57,9 @@ function replaceNewlinesWithLiteralLines(content: string) {
     .map((value, idx) => (idx % 2 === 0 ? value : literalline));
 }
 
-const printer: Printer<XMLAst> = {
+const printer: Printer = {
   embed,
-  print(path, opts: XMLOptions, print) {
+  print(path, opts, print) {
     const node = path.getValue();
 
     switch (node.name) {
@@ -79,22 +77,25 @@ const printer: Printer<XMLAst> = {
           .map((value, index) => (index % 2 === 0 ? value : literalline));
       }
       case "content": {
-        let fragments = path.call((childrenPath) => {
-          let response: { offset: number; printed: Doc }[] = [];
-          const children =
-            childrenPath.getValue() as any as ContentCstNodeExt["children"];
+        const nodePath = path as Path<typeof node>;
+
+        let fragments = nodePath.call((childrenPath) => {
+          let response: Fragment[] = [];
+          const children = childrenPath.getValue();
 
           if (children.CData) {
-            response = response.concat(path.map(printIToken, "CData"));
+            response = response.concat(childrenPath.map(printIToken, "CData"));
           }
 
           if (children.Comment) {
-            response = response.concat(path.map(printIToken, "Comment"));
+            response = response.concat(
+              childrenPath.map(printIToken, "Comment")
+            );
           }
 
           if (children.chardata) {
             response = response.concat(
-              path.map(
+              childrenPath.map(
                 (charDataPath) => ({
                   offset: charDataPath.getValue().location!.startOffset,
                   printed: print(charDataPath)
@@ -106,7 +107,7 @@ const printer: Printer<XMLAst> = {
 
           if (children.element) {
             response = response.concat(
-              path.map(
+              childrenPath.map(
                 (elementPath) => ({
                   offset: elementPath.getValue().location!.startOffset,
                   printed: print(elementPath)
@@ -118,15 +119,14 @@ const printer: Printer<XMLAst> = {
 
           if (children.PROCESSING_INSTRUCTION) {
             response = response.concat(
-              path.map(printIToken, "PROCESSING_INSTRUCTION")
+              childrenPath.map(printIToken, "PROCESSING_INSTRUCTION")
             );
           }
 
           if (children.reference) {
             response = response.concat(
-              path.map((referencePath) => {
-                const referenceNode =
-                  referencePath.getValue() as any as ReferenceCstNode;
+              childrenPath.map((referencePath) => {
+                const referenceNode = referencePath.getValue();
 
                 return {
                   offset: referenceNode.location!.startOffset,
@@ -199,17 +199,17 @@ const printer: Printer<XMLAst> = {
       }
       case "document": {
         const { docTypeDecl, element, misc, prolog } = node.children;
-        const parts: { offset: number; printed: Doc }[] = [];
+        const fragments: Fragment[] = [];
 
         if (docTypeDecl) {
-          parts.push({
+          fragments.push({
             offset: docTypeDecl[0].location!.startOffset,
             printed: path.call(print, "children", "docTypeDecl", 0)
           });
         }
 
         if (prolog) {
-          parts.push({
+          fragments.push({
             offset: prolog[0].location!.startOffset,
             printed: path.call(print, "children", "prolog", 0)
           });
@@ -218,12 +218,12 @@ const printer: Printer<XMLAst> = {
         if (misc) {
           misc.forEach((node) => {
             if (node.children.PROCESSING_INSTRUCTION) {
-              parts.push({
+              fragments.push({
                 offset: node.location!.startOffset,
                 printed: node.children.PROCESSING_INSTRUCTION[0].image
               });
             } else if (node.children.Comment) {
-              parts.push({
+              fragments.push({
                 offset: node.location!.startOffset,
                 printed: node.children.Comment[0].image
               });
@@ -232,18 +232,18 @@ const printer: Printer<XMLAst> = {
         }
 
         if (element) {
-          parts.push({
+          fragments.push({
             offset: element[0].location!.startOffset,
             printed: path.call(print, "children", "element", 0)
           });
         }
 
-        parts.sort((left, right) => left.offset - right.offset);
+        fragments.sort((left, right) => left.offset - right.offset);
 
         return [
           join(
             hardline,
-            parts.map(({ printed }) => printed)
+            fragments.map(({ printed }) => printed)
           ),
           hardline
         ];
@@ -297,24 +297,22 @@ const printer: Printer<XMLAst> = {
           opts.xmlWhitespaceSensitivity === "ignore" &&
           isWhitespaceIgnorable(content[0])
         ) {
-          const fragments = path.call(
+          const nodePath = path as Path<typeof node>;
+
+          const fragments = nodePath.call(
             (childrenPath) => {
-              const children =
-                childrenPath.getValue() as any as ContentCstNodeExt["children"];
-              let response: {
-                offset: number;
-                startLine: number;
-                endLine: number;
-                printed: Doc;
-              }[] = [];
+              const children = childrenPath.getValue();
+              let response: Fragment[] = [];
 
               if (children.Comment) {
-                response = response.concat(path.map(printIToken, "Comment"));
+                response = response.concat(
+                  childrenPath.map(printIToken, "Comment")
+                );
               }
 
               if (children.chardata) {
-                path.each((charDataPath) => {
-                  const chardata = charDataPath.getValue() as ChardataCstNode;
+                childrenPath.each((charDataPath) => {
+                  const chardata = charDataPath.getValue();
                   if (!chardata.children.TEXT) {
                     return;
                   }
@@ -348,7 +346,7 @@ const printer: Printer<XMLAst> = {
 
               if (children.element) {
                 response = response.concat(
-                  path.map((elementPath) => {
+                  childrenPath.map((elementPath) => {
                     const location = elementPath.getValue().location!;
 
                     return {
@@ -363,7 +361,7 @@ const printer: Printer<XMLAst> = {
 
               if (children.PROCESSING_INSTRUCTION) {
                 response = response.concat(
-                  path.map(printIToken, "PROCESSING_INSTRUCTION")
+                  childrenPath.map(printIToken, "PROCESSING_INSTRUCTION")
                 );
               }
 
@@ -398,17 +396,17 @@ const printer: Printer<XMLAst> = {
           }
 
           const docs: Doc[] = [];
-          let lastLine: number = fragments[0].startLine;
+          let lastLine: number = fragments[0].startLine!;
 
           fragments.forEach((node) => {
-            if (node.startLine - lastLine >= 2) {
+            if (node.startLine! - lastLine >= 2) {
               docs.push(hardline, hardline);
             } else {
               docs.push(hardline);
             }
 
             docs.push(node.printed);
-            lastLine = node.endLine;
+            lastLine = node.endLine!;
           });
 
           return group([openTag, indent(docs), hardline, closeTag]);
diff --git a/src/types.ts b/src/types.ts
index b63c9b9..69041f9 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -1,3 +1,5 @@
+import type { IToken } from "chevrotain";
+import type * as Prettier from "prettier";
 import type {
   AttributeCstNode,
   ChardataCstNode,
@@ -8,32 +10,160 @@ import type {
   ExternalIDNode,
   PrologCstNode
 } from "@xml-tools/parser";
-import type { IToken } from "chevrotain";
-import type { ParserOptions } from "prettier";
 
-export interface ContentCstNodeExt extends Omit<ContentCstNode, "children"> {
+// Override the children property of the content CST node because it is missing
+// the PROCESSING_INSTRUCTION property.
+interface ContentCstNodeExt extends Omit<ContentCstNode, "children"> {
   children: ContentCstNode["children"] & { PROCESSING_INSTRUCTION: IToken[] };
 }
 
+// Override the children property of the doctype CST node because it is missing
+// the CLOSE property.
 interface DocTypeDeclNodeExt extends Omit<DocTypeDeclNode, "children"> {
   children: DocTypeDeclNode["children"] & { CLOSE: IToken[] };
 }
 
+// Override the children property of the element CST node because its children
+// object points to the content CST node that is missing the
+// PROCESSING_INSTRUCTION property.
+interface ElementCstNodeExt extends Omit<ElementCstNode, "children"> {
+  children: Omit<ElementCstNode["children"], "content"> & {
+    content: ContentCstNodeExt[]
+  }
+}
+
+// Override the name property on the external ID CST node because it has the
+// incorrect name value.
 interface ExternalIDNodeExt extends Omit<ExternalIDNode, "name"> {
   name: "externalID";
 }
 
-export type XMLAst =
-  | AttributeCstNode
-  | ChardataCstNode
-  | ContentCstNodeExt
-  | DocTypeDeclNodeExt
-  | DocumentCstNode
-  | ElementCstNode
-  | ExternalIDNodeExt
-  | PrologCstNode;
-
-export interface XMLOptions extends ParserOptions<XMLAst> {
+// The type of elements that make up the given array T.
+type ArrayElement<T> = T extends (infer E)[] ? E : never;
+
+// A union of the properties of the given object that are arrays.
+type ArrayProperties<T> = { [K in keyof T]: T[K] extends any[] ? K : never }[keyof T];
+
+// A union of the properties of the given array T that can be used to index it.
+// If the array is a tuple, then that's going to be the explicit indices of the
+// array, otherwise it's going to just be number.
+type IndexProperties<T extends { length: number }> = IsTuple<T> extends true ? Exclude<Partial<T>["length"], T["length"]> : number;
+
+// Effectively performing T[P], except that it's telling TypeScript that it's
+// safe to do this for tuples, arrays, or objects.
+type IndexValue<T, P> = T extends any[] ? P extends number ? T[P] : never : P extends keyof T ? T[P] : never;
+
+// Determines if an object T is an array like string[] (in which case this
+// evaluates to false) or a tuple like [string] (in which case this evaluates to
+// true).
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+type IsTuple<T> = T extends [] ? true : T extends [infer First, ...infer Remain] ? IsTuple<Remain> : false;
+
+type CallProperties<T> = T extends any[] ? IndexProperties<T> : keyof T;
+type IterProperties<T> = T extends any[] ? IndexProperties<T> : ArrayProperties<T>;
+
+type CallCallback<T, U> = (path: Path<T>, index: number, value: any) => U;
+type EachCallback<T> = (path: Path<ArrayElement<T>>, index: number, value: any) => void;
+type MapCallback<T, U> = (path: Path<ArrayElement<T>>, index: number, value: any) => U;
+
+// This path interface is going to override a bunch of functions on the regular
+// prettier AstPath interface. This is because we want stricter types than the
+// current version of @types/prettier provides.
+//
+// For each of the tree walk functions (call, each, and map) this provides 5
+// strict type signatures, along with a fallback at the end if you end up
+// calling more than 5 properties deep (we don't do that but I've included it
+// for completeness).
+//
+// getParentNode is being overridden because previously it was restricted to the
+// type T of the given AST, but it's very unlikely you're going to be receiving
+// a parent node that exactly matches your current node. So for now just
+// returning any.
+// prettier-ignore
+interface StrictPath<T> {
+  call<U>(callback: CallCallback<T, U>): U;
+  call<U, P1 extends CallProperties<T>>(callback: CallCallback<IndexValue<T, P1>, U>, prop1: P1): U;
+  call<U, P1 extends keyof T, P2 extends CallProperties<T[P1]>>(callback: CallCallback<IndexValue<IndexValue<T, P1>, P2>, U>, prop1: P1, prop2: P2): U;
+  call<U, P1 extends keyof T, P2 extends CallProperties<T[P1]>, P3 extends CallProperties<IndexValue<T[P1], P2>>>(callback: CallCallback<IndexValue<IndexValue<IndexValue<T, P1>, P2>, P3>, U>, prop1: P1, prop2: P2, prop3: P3): U;
+  call<U, P1 extends keyof T, P2 extends CallProperties<T[P1]>, P3 extends CallProperties<IndexValue<T[P1], P2>>, P4 extends CallProperties<IndexValue<IndexValue<T[P1], P2>, P3>>>(callback: CallCallback<IndexValue<IndexValue<IndexValue<IndexValue<T, P1>, P2>, P3>, P4>, U>, prop1: P1, prop2: P2, prop3: P3, prop4: P4): U;
+  call<U, P extends PropertyKey>(callback: CallCallback<any, U>, prop1: P, prop2: P, prop3: P, prop4: P, ...props: P[]): U;
+
+  each(callback: EachCallback<T>): void;
+  each<P1 extends IterProperties<T>>(callback: EachCallback<IndexValue<T, P1>>, prop1: P1): void;
+  each<P1 extends keyof T, P2 extends IterProperties<T[P1]>>(callback: EachCallback<IndexValue<IndexValue<T, P1>, P2>>, prop1: P1, prop2: P2): void;
+  each<P1 extends keyof T, P2 extends IterProperties<T[P1]>, P3 extends IterProperties<IndexValue<T[P1], P2>>>(callback: EachCallback<IndexValue<IndexValue<IndexValue<T, P1>, P2>, P3>>, prop1: P1, prop2: P2, prop3: P3): void;
+  each<P1 extends keyof T, P2 extends IterProperties<T[P1]>, P3 extends IterProperties<IndexValue<T[P1], P2>>, P4 extends IterProperties<IndexValue<IndexValue<T[P1], P2>, P3>>>(callback: EachCallback<IndexValue<IndexValue<IndexValue<IndexValue<T, P1>, P2>, P3>, P4>>, prop1: P1, prop2: P2, prop3: P3, prop4: P4): void;
+  each<P extends PropertyKey>(callback: EachCallback<any>, prop1: P, prop2: P, prop3: P, prop4: P, ...props: P[]): void;
+
+  getParentNode: (count?: number | undefined) => any | null,
+
+  map<U>(callback: MapCallback<T, U>): U[];
+  map<U, P1 extends IterProperties<T>>(callback: MapCallback<IndexValue<T, P1>, U>, prop1: P1): U[];
+  map<U, P1 extends keyof T, P2 extends IterProperties<T[P1]>>(callback: MapCallback<IndexValue<IndexValue<T, P1>, P2>, U>, prop1: P1, prop2: P2): U[];
+  map<U, P1 extends keyof T, P2 extends IterProperties<T[P1]>, P3 extends IterProperties<IndexValue<T[P1], P2>>>(callback: MapCallback<IndexValue<IndexValue<IndexValue<T, P1>, P2>, P3>, U>, prop1: P1, prop2: P2, prop3: P3): U[];
+  map<U, P1 extends keyof T, P2 extends IterProperties<T[P1]>, P3 extends IterProperties<IndexValue<T[P1], P2>>, P4 extends IterProperties<IndexValue<IndexValue<T[P1], P2>, P3>>>(callback: MapCallback<IndexValue<IndexValue<IndexValue<IndexValue<T, P1>, P2>, P3>, P4>, U>, prop1: P1, prop2: P2, prop3: P3, prop4: P4): U[];
+  map<U, P extends PropertyKey>(callback: MapCallback<any, U>, prop1: P, prop2: P, prop3: P, prop4: P, ...props: P[]): U[];
+};
+
+// Exporting an overall node union type so that we can reference it in parsers
+// and everything.
+export type AnyNode = AttributeCstNode | ChardataCstNode | ContentCstNodeExt | DocTypeDeclNodeExt | DocumentCstNode | ElementCstNodeExt | ExternalIDNodeExt | PrologCstNode;
+
+// Reexporting every export from the parser so that the different node types can
+// be referenced.
+export * from "@xml-tools/parser";
+
+// Rexporting the IToken from chevrotain because that is effectively another
+// node in th tree.
+export type { IToken } from "chevrotain";
+
+// Reexporting the Doc type mostly because it's annoying to have to reference
+// type so deeply in the Prettier namespace. Also because we only really want to
+// be pulling in types from this file as they're less likely to change.
+export type Doc = Prettier.doc.builders.Doc;
+
+// This is the same embed as is present in prettier, except that it's required.
+export type Embed = (
+  path: Path<AnyNode>,
+  print: (path: Path<any>) => Doc,
+  textToDoc: (text: string, options: Options) => Doc,
+  options: Options
+) => Doc | null;
+
+// These are the regular options from prettier except they also include all of
+// the options we defined in our plugin configuration.
+export type Options = Prettier.ParserOptions<any> & {
   bracketSameLine: boolean;
   xmlWhitespaceSensitivity: "ignore" | "strict";
-}
+};
+
+// We're going to change the signature of our parse function to accept our
+// options as opposed to the generic options that don't contain the options we
+// defined in our plugin configuration.
+export type Parser = Omit<Prettier.Parser<AnyNode>, "parse"> & {
+  parse: (text: string, parsers: { [name: string]: Prettier.Parser<any> }, options: Options) => any
+};
+
+// We're overwriting a bunch of function here that walk around the tree here
+// because if you restrict the AST for the main path then presumably you're
+// printing a lower node in the tree that won't match the current AST type.
+export type Path<T> = Omit<Prettier.AstPath<T>, keyof StrictPath<T>> & StrictPath<T>;
+
+// Overwriting the parsers option so that we can again pass our defined options
+// into the parse functions.
+export type Plugin = Omit<Prettier.Plugin<AnyNode>, "parsers" | "printers"> & {
+  parsers: { [name: string]: Parser },
+  printers: { [name: string]: Printer }
+};
+
+// We're overwriting the print function with a print function that accepts the
+// options we've defined.
+export type Printer = Omit<Prettier.Printer<AnyNode>, "embed" | "print"> & {
+  embed: Embed,
+  print: (path: Path<AnyNode>, options: Options, print: Print) => Doc;
+};
+
+// This is the regular print node, except it's not restricted by the AST that
+// is passed to the parent AST. That's because when you're using it, you are not
+// typically printing the same node type again.
+export type Print = (path: Path<any>) => Doc;


Test patch:
diff --git a/test/format.test.ts b/test/format.test.ts
index ee8bb08..ac200db 100644
--- a/test/format.test.ts
+++ b/test/format.test.ts
@@ -2,16 +2,16 @@ import fs from "fs";
 import path from "path";
 import prettier from "prettier";
 
-import { XMLOptions } from "../src/types";
+import { Options } from "../src/types";
 import plugin from "../src/plugin";
 
 const fixture = fs.readFileSync(path.join(__dirname, "./fixture.xml"), "utf-8");
 
-function format(content: string, opts: Partial<XMLOptions> = {}) {
+function format(content: string, opts: Partial<Options> = {}) {
   return prettier.format(content, {
     ...opts,
     parser: "xml",
-    plugins: [plugin]
+    plugins: [plugin as any as string] // hacky but it works
   });
 }