Skip to content

Extending Demark

Aadit M Shah edited this page Oct 22, 2022 · 1 revision

In this tutorial, we'll extend Demark to create a new markup language. We highly recommend that you create a new TypeScript project and follow along.

If you are new to Demark, we recommend that you first read the Introduction to Demark article.

Parsing Demark Files

So, you have written your first real world Demark file. What to do next? How do you use it? Well, we'll first need to parse the Demark file.

To do so, we'll need to install the @demark/parser package. This package contains built-in TypeScript declarations.

npm i @demark/parser

# Or if you're using Yarn

yarn add @demark/parser

Now, suppose we want to parse the following Demark document. Save this document in your TypeScript project.

https://raw.githubusercontent.com/aaditmshah/demark/main/examples/why-create-your-own-eslint-config.dm

Create a new TypeScript file and paste the following code.

import { readFile } from "node:fs/promises";
import { inspect } from "node:util";
import * as dm from "@demark/parser";

const main = async () => {
  const bytes = await readFile("./path/to/why-create-your-own-eslint-config.dm");
  const result = dm.parse(bytes);
  console.log(inspect(result, false, null, true));
};

main().catch((error) => {
  console.error(inspect(error, false, null, true));
});

When you run this program, you should see the following output.

Click to expand the output.
[
  {
    tag: 'article',
    content: [ '3 Reasons Why You Should Create Your Own ESLint Config' ]
  },
  { tag: 'tag', content: [ 'eslint' ] },
  { tag: 'tag', content: [ 'eslint-config' ] },
  { tag: 'tag', content: [ 'ivory' ] },
  {
    tag: 'content',
    content: [
      '\n  ',
      [
        {
          tag: 'paragraph',
          content: [
            'I recently created my own eslint config called ',
            [
              { tag: 'link', content: [ 'Ivory' ] },
              {
                tag: 'href',
                content: [ 'https://www.npmjs.com/package/eslint-config-ivory' ]
              }
            ],
            ". It's my first open-source project. I decided that since I'm going to be a full-time open-source developer, I need to adhere to a single code style for all my repositories. So, why didn't I go with a popular eslint config like ",
            [
              { tag: 'link', content: [ 'Airbnb' ] },
              {
                tag: 'href',
                content: [ 'https://github.com/airbnb/javascript' ]
              }
            ],
            '? Here are 3 reasons why I created my own eslint config, and why you should consider doing the same.'
          ]
        }
      ],
      '\n  ',
      [
        {
          tag: 'section',
          content: [ '1. You Will Have Full Control Over Your Code Style' ]
        },
        {
          tag: 'content',
          content: [
            '\n    ',
            [
              {
                tag: 'paragraph',
                content: [
                  "I'm a very opinionated software developer. For example, I prefer writing ",
                  [ { tag: 'code', content: [ 'for…of' ] } ],
                  ' loops over ',
                  [ { tag: 'code', content: [ '.forEach' ] } ],
                  '. However, the Airbnb eslint config disallows ',
                  [ { tag: 'code', content: [ 'for…of' ] } ],
                  " loops. So, what's a developer supposed to do? Either you give in and use ",
                  [ { tag: 'code', content: [ '.forEach' ] } ],
                  ', or you explicitly override the rule.'
                ]
              }
            ],
            '\n    ',
            [
              {
                tag: 'paragraph',
                content: [
                  'Unfortunately, overriding a rule is not always as simple as turning it ',
                  [ { tag: 'code', content: [ '"off"' ] } ],
                  '. For example, the Airbnb eslint config uses the ',
                  [
                    {
                      tag: 'link',
                      content: [
                        [
                          {
                            tag: 'code',
                            content: [ 'no-restricted-syntax' ]
                          }
                        ]
                      ]
                    },
                    {
                      tag: 'href',
                      content: [
                        'https://eslint.org/docs/latest/rules/no-restricted-syntax'
                      ]
                    }
                  ],
                  ' rule to disallow ',
                  [ { tag: 'code', content: [ 'for…of' ] } ],
                  ' loops. If you turn it ',
                  [ { tag: 'code', content: [ '"off"' ] } ],
                  ' then you will inadvertently allow ',
                  [ { tag: 'code', content: [ 'for…in' ] } ],
                  ' loops, labels, and the ',
                  [ { tag: 'code', content: [ 'with' ] } ],
                  ' statement. Those are all things I want to disallow. Tweaking the rule to only allow ',
                  [ { tag: 'code', content: [ 'for…of' ] } ],
                  ' loops is complicated.'
                ]
              }
            ],
            '\n    ',
            [
              {
                tag: 'paragraph',
                content: [
                  "Now, though it might be complicated to tailor the Airbnb eslint config to suit your code style, it's nevertheless possible. But customized eslint configs suffer from another big problem — updates. Every time Airbnb updates its eslint config, there's a possibility that the updates might conflict with your code style. However, if you don't update then you skip out on configuring new rules."
                ]
              }
            ],
            '\n    ',
            [
              {
                tag: 'paragraph',
                content: [
                  "All these problems can be avoided if you create your own eslint config. Since you have full control of the eslint config, there's no possibility of subverting your code style. Furthermore, you can set up automated tests and workflows to ensure that your eslint config is always consistent and up to date. For example, the ",
                  [
                    { tag: 'link', content: [ 'Alloy' ] },
                    {
                      tag: 'href',
                      content: [
                        'https://github.com/AlloyTeam/eslint-config-alloy#philosophy'
                      ]
                    }
                  ],
                  ' eslint config runs automated weekly checks for new rules added by eslint and its plugins. It also checks whether its rules conflict with ',
                  [
                    { tag: 'link', content: [ 'Prettier' ] },
                    {
                      tag: 'href',
                      content: [ 'https://prettier.io/' ]
                    }
                  ],
                  ' or are deprecated.'
                ]
              }
            ],
            '\n    ',
            [
              {
                tag: 'paragraph',
                content: [
                  "Sounds like a lot of work? It can indeed be daunting to create your own eslint config and manually configure all the rules to suit your code style. For example, I spent an entire 40-hour work week configuring all the rules in Ivory. But unlike me, you don't have to pay the cost upfront. You can create a fork of Ivory, start using it, and configure it over time to suit your code style."
                ]
              }
            ],
            '\n  '
          ]
        }
      ],
      '\n  ',
      [
        {
          tag: 'section',
          content: [ '2. You Will Have Consistency Across All Repositories' ]
        },
        {
          tag: 'content',
          content: [
            '\n    ',
            [
              {
                tag: 'paragraph',
                content: [
                  'Creating your own eslint config does come at the significant cost of manually configuring all the rules. However, the benefit is that you only have to write the config once for all your repositories. For every new repository, you should only have to extend your eslint config. You should never have to override any rules, as that would make your code style inconsistent across repositories.'
                ]
              }
            ],
            '\n    ',
            [
              {
                tag: 'paragraph',
                content: [
                  "I know from experience that it's very easy for developers to create multiple repositories with inconsistent code styles. It's effortless for developers to override rules on a per-repository basis. Especially when you're working in a large organization with multiple teams. Using an external eslint config like Airbnb just encourages developers to override rules on a per-repository basis."
                ]
              }
            ],
            '\n    ',
            [
              {
                tag: 'paragraph',
                content: [
                  'Now, inconsistent code styles would not be a major issue if eslint rules only enforced a stylistic preference, like ',
                  [ { tag: 'code', content: [ 'for…of' ] } ],
                  ' loops vs ',
                  [ { tag: 'code', content: [ '.forEach' ] } ],
                  '. However, a lot of eslint rules enforce a particular code style to prevent potential bugs. For example, I have a strong preference to ban type assertions in TypeScript by enabling the ',
                  [
                    {
                      tag: 'link',
                      content: [
                        [
                          {
                            tag: 'code',
                            content: [ 'consistent-type-assertions' ]
                          }
                        ]
                      ]
                    },
                    {
                      tag: 'href',
                      content: [
                        'https://typescript-eslint.io/rules/consistent-type-assertions'
                      ]
                    }
                  ],
                  ' rule and setting the ',
                  [ { tag: 'code', content: [ 'assertionStyle' ] } ],
                  ' to ',
                  [ { tag: 'code', content: [ '"never"' ] } ],
                  '. This prevents a lot of bugs by forcing developers to fix errors reported by the type checker. If this code style was inconsistent across repositories then bugs may slip through.'
                ]
              }
            ],
            '\n    ',
            [
              {
                tag: 'paragraph',
                content: [
                  'When you create your own eslint config, you should discourage developers from overriding rules on a per-repository basis. Instead, you should encourage developers to raise an issue in your eslint config repository. This allows you to debate the proposed code style, update the eslint config in a disciplined manner, and most importantly maintain consistency across repositories.'
                ]
              }
            ],
            '\n  '
          ]
        }
      ],
      '\n  ',
      [
        {
          tag: 'section',
          content: [ '3. You Can Have Customization With Preset Configs' ]
        },
        {
          tag: 'content',
          content: [
            '\n    ',
            [
              {
                tag: 'paragraph',
                content: [
                  "While maintaining consistency across repositories is important, not all repositories require the same configuration rules. For example, you may only want to activate React rules for front-end applications. Fortunately, creating your own eslint config doesn't mean that you have to give up customization across repositories. You can have granular configs and combine them as required."
                ]
              }
            ],
            '\n    ',
            [
              {
                tag: 'paragraph',
                content: [
                  'Breaking big things into smaller things and then recombining them to form the original thing is a common theme in computer science. Programmers know this concept by many names such as ',
                  [
                    { tag: 'link', content: [ 'dynamic programming' ] },
                    {
                      tag: 'href',
                      content: [
                        'https://en.wikipedia.org/wiki/Dynamic_programming'
                      ]
                    }
                  ],
                  ', the ',
                  [
                    { tag: 'link', content: [ 'combinator pattern' ] },
                    {
                      tag: 'href',
                      content: [ 'https://wiki.haskell.org/Combinator_pattern' ]
                    }
                  ],
                  ', and ',
                  [
                    { tag: 'link', content: [ 'decomposition' ] },
                    {
                      tag: 'href',
                      content: [ 'http://worrydream.com/LearnableProgramming/' ]
                    }
                  ],
                  ' and ',
                  [
                    { tag: 'link', content: [ 'recomposition' ] },
                    {
                      tag: 'href',
                      content: [
                        'https://www.cs.kent.ac.uk/people/staff/dat/miranda/whyfp90.pdf'
                      ]
                    }
                  ],
                  '. This technique has become popular in modern times. For example, the core concept of ',
                  [
                    { tag: 'link', content: [ 'Tailwind CSS' ] },
                    {
                      tag: 'href',
                      content: [ 'https://tailwindcss.com/' ]
                    }
                  ],
                  ', the most popular CSS framework, is combining simple style classes to build more complex styles.'
                ]
              }
            ],
            '\n    ',
            [
              {
                tag: 'paragraph',
                content: [
                  'The same concept can be applied to eslint configs. We already have a way of combining multiple eslint configs using the ',
                  [ { tag: 'code', content: [ 'extends' ] } ],
                  ' property. So, instead of creating a single monolithic eslint config, we can create multiple eslint configs at the level of granularity we desire. We can then combine them using the ',
                  [ { tag: 'code', content: [ 'extends' ] } ],
                  ' property into several preset configs for different kinds of repositories. The ',
                  [
                    { tag: 'link', content: [ 'Canonical' ] },
                    {
                      tag: 'href',
                      content: [
                        'https://github.com/gajus/eslint-config-canonical#example-configuration'
                      ]
                    }
                  ],
                  ' eslint config has some excellent examples on how to combine multiple configs.'
                ]
              }
            ],
            '\n  '
          ]
        }
      ],
      '\n  ',
      [
        {
          tag: 'section',
          content: [ 'So Go Ahead and Create Your Own ESLint Config' ]
        },
        {
          tag: 'content',
          content: [
            '\n    ',
            [
              {
                tag: 'paragraph',
                content: [
                  "Using a popular eslint config like Airbnb is easy when you want to get started as soon as possible. However, the return on investment sharply declines when you start overriding rules, copying your eslintrc file, and trying to maintain consistency across repositories. Having your own eslint config solves all these problems. And you don't need to start from scratch. Just fork ",
                  [
                    { tag: 'link', content: [ 'Ivory' ] },
                    {
                      tag: 'href',
                      content: [
                        'https://www.npmjs.com/package/eslint-config-ivory'
                      ]
                    }
                  ],
                  ' and get going.'
                ]
              }
            ],
            '\n  '
          ]
        }
      ],
      '\n'
    ]
  }
]

This is the abstract syntax tree generated by the Demark parser. The output is of type TaggedContentGroup.

type Stream<A> = [A, ...A[]];
type Data = string | TaggedContentGroup;
type Content = Stream<Data>;
interface TaggedContent {
    tag: string;
    content: Content;
}
type TaggedContentGroup = Stream<TaggedContent>;

You can use this AST directly. However, you will most likely want to convert it into a nicer format.

Extending Demark

Extending Demark to create a new markup language is easy. All you need to do is define the schema of the new markup language. The @demark/parser package provides a set of useful combinators to create these schemas. Furthermore, these schemas can be used to convert the AST generated by the Demark parser into a nicer format.

Before we write the schemas for our new markup language, let's examine the content of the why-create-your-own-eslint-config.dm file. This Demark file has 5 kinds of tag groups.

  1. article~tag~content This is the top level tag group. The article tag contains the title of the article. It is followed by one or more tag tags, which contain the categories that the article belongs to. Finally, we have the content tag which contains the content of the article. The content must be one or more introductory paragraphs followed by one or more sections.
  2. section~content The section tag contains the title of the section. The content tag contains one or more paragraphs.
  3. paragraph The paragraph tag contains one or more text, link, or code tags.
  4. link~href The link tag contains the anchor text of the hyperlink. The anchor text may also be some code. The href tag contains the URL of the hypertext reference.
  5. code The code tag contains inline code.

Now that we have an understanding of the structure of our Demark document, we can represent the structure as TypeScript interfaces.

interface Code {
  tag: "Code";
  code: string;
}

interface Link {
  tag: "Link";
  anchor: string | Code;
  href: string;
}

interface Paragraph {
  tag: "Paragraph";
  content: dm.Stream<string | Code | Link>;
}

interface Section {
  tag: "Section";
  title: string;
  paragraphs: dm.Stream<Paragraph>;
}

interface ArticleContent {
  tag: "ArticleContent";
  introduction: dm.Stream<Paragraph>;
  sections: dm.Stream<Section>;
}

interface Article {
  tag: "Article";
  title: string;
  tags: dm.Stream<string>;
  content: ArticleContent;
}

Next, we'll create data constructors for each interface.

const createCode = (code: string): Code => ({ tag: "Code", code });

const createLink = (anchor: string | Code, href: string): Link => ({
  tag: "Link",
  anchor,
  href
});

const createParagraph = (
  content: dm.Stream<string | Code | Link>
): Paragraph => ({
  tag: "Paragraph",
  content
});

const createSection = (
  title: string,
  paragraphs: dm.Stream<Paragraph>
): Section => ({
  tag: "Section",
  title,
  paragraphs
});

const createArticleContent = (
  introduction: dm.Stream<Paragraph>,
  sections: dm.Stream<Section>
): ArticleContent => ({
  tag: "ArticleContent",
  introduction,
  sections
});

const createArticle = (
  title: string,
  tags: dm.Stream<string>,
  content: ArticleContent
): Article => ({
  tag: "Article",
  title,
  tags,
  content
});

Finally, we'll write the schemas for each interface.

const code = dm.group(dm.map(createCode, dm.tagged("code", dm.text)));

const link = dm.group(
  dm.map(
    createLink,
    dm.tagged("link", dm.alt(dm.text, code)),
    dm.tagged("href", dm.text)
  )
);

const paragraph = dm.group(
  dm.map(
    createParagraph,
    dm.tagged("paragraph", dm.some(dm.alt(dm.text, code, link)))
  )
);

const section = dm.group(
  dm.map(
    createSection,
    dm.tagged("section", dm.text),
    dm.tagged("content", dm.some(paragraph))
  )
);

const articleContent = dm.map(
  createArticleContent,
  dm.some(paragraph),
  dm.some(section)
);

const article = dm.map(
  createArticle,
  dm.tagged("article", dm.text),
  dm.some(dm.tagged("tag", dm.text)),
  dm.tagged("content", articleContent)
);

We're using the combinators map, alt, some, text, group, and tagged from @demark/parsers to define our schemas.

  1. The text combinator is the simplest combinator, which matches textual content.
  2. The tagged combinator matches a tag. It requires the tag name to match and the schema of the tagged content.
  3. The group combinator is used denote that a bunch of tags belong to the same tag group.
  4. The map combinator is used to collect and transform the results of several combinators. It's used to group tags.
  5. The alt combinator is used to create a disjunction of schemas. For example, the link anchor text can be either text or code.
  6. The some combinator is used to denote one or more of something. For example, a section has one or more paragraphs.

The @demark/parsers package has a few additional combinators which we did not use.

  1. The many combinator is used to denote zero or more of something.
  2. The pure and bind combinators are used for monadic decoding.

Anyway, now that we have defined our schemas we can update our main function to transform the parsed result.

const main = async () => {
  const bytes = await readFile("./path/to/why-create-your-own-eslint-config.dm");
  const result = dm.decode(article, dm.parse(bytes));
  console.log(inspect(result, false, null, true));
};

When you run this program, you should see the following output.

Click to expand the output.
{
  tag: 'Article',
  title: '3 Reasons Why You Should Create Your Own ESLint Config',
  tags: [ 'eslint', 'eslint-config', 'ivory' ],
  content: {
    tag: 'ArticleContent',
    introduction: [
      {
        tag: 'Paragraph',
        content: [
          'I recently created my own eslint config called ',
          {
            tag: 'Link',
            anchor: 'Ivory',
            href: 'https://www.npmjs.com/package/eslint-config-ivory'
          },
          ". It's my first open-source project. I decided that since I'm going to be a full-time open-source developer, I need to adhere to a single code style for all my repositories. So, why didn't I go with a popular eslint config like ",
          {
            tag: 'Link',
            anchor: 'Airbnb',
            href: 'https://github.com/airbnb/javascript'
          },
          '? Here are 3 reasons why I created my own eslint config, and why you should consider doing the same.'
        ]
      }
    ],
    sections: [
      {
        tag: 'Section',
        title: '1. You Will Have Full Control Over Your Code Style',
        paragraphs: [
          {
            tag: 'Paragraph',
            content: [
              "I'm a very opinionated software developer. For example, I prefer writing ",
              { tag: 'Code', code: 'for…of' },
              ' loops over ',
              { tag: 'Code', code: '.forEach' },
              '. However, the Airbnb eslint config disallows ',
              { tag: 'Code', code: 'for…of' },
              " loops. So, what's a developer supposed to do? Either you give in and use ",
              { tag: 'Code', code: '.forEach' },
              ', or you explicitly override the rule.'
            ]
          },
          {
            tag: 'Paragraph',
            content: [
              'Unfortunately, overriding a rule is not always as simple as turning it ',
              { tag: 'Code', code: '"off"' },
              '. For example, the Airbnb eslint config uses the ',
              {
                tag: 'Link',
                anchor: { tag: 'Code', code: 'no-restricted-syntax' },
                href: 'https://eslint.org/docs/latest/rules/no-restricted-syntax'
              },
              ' rule to disallow ',
              { tag: 'Code', code: 'for…of' },
              ' loops. If you turn it ',
              { tag: 'Code', code: '"off"' },
              ' then you will inadvertently allow ',
              { tag: 'Code', code: 'for…in' },
              ' loops, labels, and the ',
              { tag: 'Code', code: 'with' },
              ' statement. Those are all things I want to disallow. Tweaking the rule to only allow ',
              { tag: 'Code', code: 'for…of' },
              ' loops is complicated.'
            ]
          },
          {
            tag: 'Paragraph',
            content: [
              "Now, though it might be complicated to tailor the Airbnb eslint config to suit your code style, it's nevertheless possible. But customized eslint configs suffer from another big problem — updates. Every time Airbnb updates its eslint config, there's a possibility that the updates might conflict with your code style. However, if you don't update then you skip out on configuring new rules."
            ]
          },
          {
            tag: 'Paragraph',
            content: [
              "All these problems can be avoided if you create your own eslint config. Since you have full control of the eslint config, there's no possibility of subverting your code style. Furthermore, you can set up automated tests and workflows to ensure that your eslint config is always consistent and up to date. For example, the ",
              {
                tag: 'Link',
                anchor: 'Alloy',
                href: 'https://github.com/AlloyTeam/eslint-config-alloy#philosophy'
              },
              ' eslint config runs automated weekly checks for new rules added by eslint and its plugins. It also checks whether its rules conflict with ',
              {
                tag: 'Link',
                anchor: 'Prettier',
                href: 'https://prettier.io/'
              },
              ' or are deprecated.'
            ]
          },
          {
            tag: 'Paragraph',
            content: [
              "Sounds like a lot of work? It can indeed be daunting to create your own eslint config and manually configure all the rules to suit your code style. For example, I spent an entire 40-hour work week configuring all the rules in Ivory. But unlike me, you don't have to pay the cost upfront. You can create a fork of Ivory, start using it, and configure it over time to suit your code style."
            ]
          }
        ]
      },
      {
        tag: 'Section',
        title: '2. You Will Have Consistency Across All Repositories',
        paragraphs: [
          {
            tag: 'Paragraph',
            content: [
              'Creating your own eslint config does come at the significant cost of manually configuring all the rules. However, the benefit is that you only have to write the config once for all your repositories. For every new repository, you should only have to extend your eslint config. You should never have to override any rules, as that would make your code style inconsistent across repositories.'
            ]
          },
          {
            tag: 'Paragraph',
            content: [
              "I know from experience that it's very easy for developers to create multiple repositories with inconsistent code styles. It's effortless for developers to override rules on a per-repository basis. Especially when you're working in a large organization with multiple teams. Using an external eslint config like Airbnb just encourages developers to override rules on a per-repository basis."
            ]
          },
          {
            tag: 'Paragraph',
            content: [
              'Now, inconsistent code styles would not be a major issue if eslint rules only enforced a stylistic preference, like ',
              { tag: 'Code', code: 'for…of' },
              ' loops vs ',
              { tag: 'Code', code: '.forEach' },
              '. However, a lot of eslint rules enforce a particular code style to prevent potential bugs. For example, I have a strong preference to ban type assertions in TypeScript by enabling the ',
              {
                tag: 'Link',
                anchor: { tag: 'Code', code: 'consistent-type-assertions' },
                href: 'https://typescript-eslint.io/rules/consistent-type-assertions'
              },
              ' rule and setting the ',
              { tag: 'Code', code: 'assertionStyle' },
              ' to ',
              { tag: 'Code', code: '"never"' },
              '. This prevents a lot of bugs by forcing developers to fix errors reported by the type checker. If this code style was inconsistent across repositories then bugs may slip through.'
            ]
          },
          {
            tag: 'Paragraph',
            content: [
              'When you create your own eslint config, you should discourage developers from overriding rules on a per-repository basis. Instead, you should encourage developers to raise an issue in your eslint config repository. This allows you to debate the proposed code style, update the eslint config in a disciplined manner, and most importantly maintain consistency across repositories.'
            ]
          }
        ]
      },
      {
        tag: 'Section',
        title: '3. You Can Have Customization With Preset Configs',
        paragraphs: [
          {
            tag: 'Paragraph',
            content: [
              "While maintaining consistency across repositories is important, not all repositories require the same configuration rules. For example, you may only want to activate React rules for front-end applications. Fortunately, creating your own eslint config doesn't mean that you have to give up customization across repositories. You can have granular configs and combine them as required."
            ]
          },
          {
            tag: 'Paragraph',
            content: [
              'Breaking big things into smaller things and then recombining them to form the original thing is a common theme in computer science. Programmers know this concept by many names such as ',
              {
                tag: 'Link',
                anchor: 'dynamic programming',
                href: 'https://en.wikipedia.org/wiki/Dynamic_programming'
              },
              ', the ',
              {
                tag: 'Link',
                anchor: 'combinator pattern',
                href: 'https://wiki.haskell.org/Combinator_pattern'
              },
              ', and ',
              {
                tag: 'Link',
                anchor: 'decomposition',
                href: 'http://worrydream.com/LearnableProgramming/'
              },
              ' and ',
              {
                tag: 'Link',
                anchor: 'recomposition',
                href: 'https://www.cs.kent.ac.uk/people/staff/dat/miranda/whyfp90.pdf'
              },
              '. This technique has become popular in modern times. For example, the core concept of ',
              {
                tag: 'Link',
                anchor: 'Tailwind CSS',
                href: 'https://tailwindcss.com/'
              },
              ', the most popular CSS framework, is combining simple style classes to build more complex styles.'
            ]
          },
          {
            tag: 'Paragraph',
            content: [
              'The same concept can be applied to eslint configs. We already have a way of combining multiple eslint configs using the ',
              { tag: 'Code', code: 'extends' },
              ' property. So, instead of creating a single monolithic eslint config, we can create multiple eslint configs at the level of granularity we desire. We can then combine them using the ',
              { tag: 'Code', code: 'extends' },
              ' property into several preset configs for different kinds of repositories. The ',
              {
                tag: 'Link',
                anchor: 'Canonical',
                href: 'https://github.com/gajus/eslint-config-canonical#example-configuration'
              },
              ' eslint config has some excellent examples on how to combine multiple configs.'
            ]
          }
        ]
      },
      {
        tag: 'Section',
        title: 'So Go Ahead and Create Your Own ESLint Config',
        paragraphs: [
          {
            tag: 'Paragraph',
            content: [
              "Using a popular eslint config like Airbnb is easy when you want to get started as soon as possible. However, the return on investment sharply declines when you start overriding rules, copying your eslintrc file, and trying to maintain consistency across repositories. Having your own eslint config solves all these problems. And you don't need to start from scratch. Just fork ",
              {
                tag: 'Link',
                anchor: 'Ivory',
                href: 'https://www.npmjs.com/package/eslint-config-ivory'
              },
              ' and get going.'
            ]
          }
        ]
      }
    ]
  }
}

As you can see, the output is much nicer than the original AST. Furthermore, since we defined a schema for our new markup language the decoding will fail if the Demark document doesn't match our schema.

Clone this wiki locally