Configuration

Configuration file

The configuration file is the rules and/or options that load on markuplint executes. That is usually automatic loading, but you also can load the config expected explicitly using CLI or API.

The automatic loading is recursively searching up from a directory that the target exists. In other words, It is applying that configure files that the closest each the target.

Format and Filename

You can get even if the filename is not .markuplintrc.

The priority applied names are:

  • markuplint property in package.json
  • markuplintrc.json, markuplintrc.yaml, markuplintrc.yml, markuplintrc.js, or markuplintrc.cjs
  • markuplint.config.js or markuplint.config.cjs

.markuplintrc's format is JSON and also YAML.

Properties

{
  "extends": [],
  "plugins": {},
  "parser": {},
  "parserOptions": {},
  "specs": [],
  "excludeFiles": [],
  "rules": {},
  "nodeRules": [],
  "childNodeRules": [],
  "overrides": {}
}

Specification about paths

extends, plugins, specs, specs, and excludeFiles can specify paths. In extends, plugins, parser, and specs four, it can specify a package name instead of a path.

First, it tries to import it as a package. If it fails, such as the package doesn't exist, or the strings are not a package, it resolves strings as just a path. These paths are resolved to absolute paths internally. A relative path becomes an absolute path on the base of the config file path being had it.

extends

If you specify other config file paths, it merges the current setting with them.

{
  "extends": [
    // As a local file
    "../../.markuplintrc",
    // As a package
    "third-party-config"
  ]
}

The name added the prefix markuplint: loads the config provided from markuplint core. There is only one kind of configs is markuplint:recommended that it can provide currently.

{
  "extends": ["markuplint:recommended"]
}

The name added the prefix plugin: loads the config provided from any plugins. The before the slash is a namespace determined by the plugin. The after the slash is the unique config name on the plugin.

{
  "extends": ["plugin:third-party-plugin-name/config-name"],
  "plugins": ["third-party-plugin"]
}

Interface

interface Config {
  extends?: string[];
}

plugins

You can load any plugins. Specify a package name or a path. Can specify settings if the plugin has it.

{
  "plugins": [
    "third-party-plugin",
    "@third-party/markuplint-plugin",
    {
      "name": "third-party-plugin2",
      "settings": {
        "foo": "bar"
      }
    },
    "./path/to/local-plugin.js",
    {
      "name": "./path/to/local-plugin.js2",
      "settings": {
        "foo": "bar"
      }
    }
  ]
}

Interface

interface Config {
  plugins?: (
    | string
    | {
        name: string;
        settings?: Record<string, unknown>;
      }
  )[];
}

parser

Specify a regex to the key, and the parser file path or a package name to the value. The regex should be specify it matches the target lintee file (ex., the extension part).

{
  "parser": {
    "\\.pug$": "@markuplint/pug-parser",
    "\\.[jt]sx?$": "@markuplint/jsx-parser",
    "\\.vue$": "@markuplint/vue-parser",
    "\\.svelte$": "@markuplint/svelte-parser",
    "\\.ext$": "./path/to/custom-parser/any-lang.js"
  }
}

Interface

interface Config {
  parser?: {
    [regex: string]: string;
  };
}

parserOptions

{
  "parserOptions": {
    "ignoreFrontMatter": true
  }
}

ignoreFrontMatter

  • Type: boolean
  • Default: false

When set true the parser ignores the Front Matter format part of the source code.

---
prop: value
---

<html>
  ...
</html>

Interface

interface Config {
  parserOptions?: {
    ignoreFrontMatter?: boolean;
  };
}

specs

Specify a regex to the key, and the spec file path or a package name to the value. The regex should be specify it matches the target lintee file (ex., the extension part).

{
  "specs": {
    "\\.vue$": "@markuplint/vue-spec",
    "\\.ext$": "./path/to/custom-specs/any-lang.js"
  }
}

Or, You can add those to this array. But this is the format for version 1.x, so it's deprecated.

{
  // Depraceted
  "specs": ["@markuplint/vue-spec", "./path/to/custom-specs/any-lang"]
}

Interface

interface Config {
  specs?:
    | {
        [regex: string]: string;
      }
    /**
     * @Depraceted
     */
    | string[]
    /**
     * @Depraceted
     */
    | string;
}

excludeFiles

It can exclude files if you need them. The values require the relative path from the configuration file or the absolute path. Paths can be glob format.

{
  "excludeFiles": ["./ignore.html", "./ignore/**/*.html"]
}

Interface

interface Config {
  excludeFiles?: string[];
}

rules

And add some rules to this property.

{
  "rules": {
    "rule-name": "value" // Add to here or more
  }
}

Specify a value for each rule. Those are any strings, any number, and an array. The rule becomes disabled if specify false. It evaluates as the default value if specify true. Otherwise, you can specify details by structure:

{
  "rules": {
    "rule-name": {
      // Type: string | number | boolean | Array
      "value": "any-value",

      // Optional, "error" or "warning"
      "severity": "error",

      // Optional
      "option": {
        "any-option": "any-optional-value"
      }
    }
  }
}

The details of each rule are said from the Rules page.

For example, setting of the rule of invalid-attr:

{
  "rule": {
    "invalid-attr": {
      "value": true,
      "severity": "warning",
      "option": {
        "attrs": {
          "x-attr": {
            "enum": ["value1", "value2", "value3"]
          }
        }
      }
    }
  }
}

About the rule name

There are cases in which a rule name includes a slash. In that case, it indicates the rule is from a plugin. The before the slash is a namespace determined by the plugin. The after the slash is the unique rule name on the plugin.

{
  "plugins": ["third-party-plugin", "./path/to/local-plugin.js"],
  "rules": {
    "core-rule-name": true,
    "third-party-plugin/rule-name": true,
    "named-plugin-imported-form-local/rule-name": true
  }
}

Interface

interface Config {
  rules?: {
    [ruleName: string]: Rule<T, O>;
  };
}

type Rule<T, O> =
  | boolean
  | T
  | {
      severity?: 'error' | 'warning' | 'info';
      value?: T;
      option?: O;
      reason?: string;
    };

nodeRules & childNodeRules

nodeRules

If you want only any specific element to apply some rule, you can specify by this property. Be careful to the value is an array. You can specify the rules property of this property like the rules property of the root.

{
  "nodeRules": [
    {
      "selector": "main",
      "rules": {
        "class-naming": "/[a-z]+(__[a-z]+)?/"
      }
    }
  ]
}

childNodeRules

If you want any specific element's descendants to apply some rule, you can specify by this property. If specifies true to the inheritance property, affects all descendant nodes of the target element, if not, affects only child nodes. Be careful; This value is an array. You can specify the rules property of this property like the rules property of the root.

{
  "rules": {
    "character-reference": true
  },
  "childNodeRules": [
    {
      "selector": ".ignoreClass",
      "inheritance": true,
      "rules": {
        "character-reference": false
      }
    }
  ]
}
Interface
interface Config {
  nodeRules?: (
    | {
        selector: string;
        rules: {
          [ruleName: string]: Rule<T, O>;
        };
      }
    | {
        regexSelector: RegexSelector;
        rules: {
          [ruleName: string]: Rule<T, O>;
        };
      }
  )[];

  childNodeRules?: (
    | {
        selector: string;
        inheritance?: boolean;
        rules: {
          [ruleName: string]: Rule<T, O>;
        };
      }
    | {
        regexSelector: RegexSelector;
        inheritance?: boolean;
        rules: {
          [ruleName: string]: Rule<T, O>;
        };
      }
  )[];
}

type RegexSelector = {
  nodeName?: string;
  attrName?: string;
  attrValue?: string;
  combination?: RegexSelector & {
    combinator: ' ' | '>' | '+' | '~' | ':has(+)' | ':has(~)';
  };
};

selector

Support syntax and operators:

Selector TypeCode ExampleSupport
Universal selector*
Type selectordiv
ID selector#id
Class selector.class
Attribute selector[data-attr]
Attribute selector, Exact match[data-attr=value]
Attribute selector, Include whitespace separated[data-attr~=value]
Attribute selector, Subcode match[data-attr|=value]
Attribute selector, Partial match[data-attr*=value]
Attribute selector, Forward match[data-attr^=value]
Attribute selector, Backward match[data-attr$=value]
Negation pseudo-class:not(div)
Matches-Any pseudo-class:is(div)
Specificity-adjustment pseudo-class:where(div)
Relational pseudo-class:has(div) :has(> div)
Directionality pseudo-class:dir(ltr)
Language pseudo-class:lang(en)
Hyperlink pseudo-class:any-link
Link History pseudo-class:link :visited
Local link pseudo-class:local-link
Target pseudo-class:target
Target container pseudo-class:target-within
Reference element pseudo-class:scope
Current-element pseudo-class:current :current(div)
Past pseudo-class:past
Future pseudo-class:future
Interactive pseudo-class:active :hover :focus :focus-within :focus-visible
Enable and disable pseudo-class:enable :disable
Mutability pseudo-class:read-write :read-only
Placeholder-shown pseudo-class:placeholder-shown
Default-option pseudo-class:default
Selected-option pseudo-class:checked
Indeterminate value pseudo-class:indeterminate
Validity pseudo-class:valid :invalid
Range pseudo-class:in-range :out-of-range
Optionality pseudo-class:required :optional
Empty-Value pseudo-class:blank
User-interaction pseudo-class:user-invalid
Root pseudo-class:root
Empty pseudo-class:empty
Nth-child pseudo-class:nth-child(2) :nth-last-child(2) :first-child :last-child :only-child
Nth-child pseudo-class (of El Syntax):nth-child(2 of div) :nth-last-child(2 of div)
Nth-of-type pseudo-class:nth-of-type(2) :nth-last-of-type(2) :first-of-type :last-of-type :only-of-type
Nth-col pseudo-class:nth-col(2) :nth-last-col(2)
Pseudo elements::before ::after
Descendant combinatordiv span
Child combinatordiv > span
Next-sibling combinatordiv + span
Subsequent-sibling combinatordiv ~ span
Column combinatordiv || span
Multiple selectorsdiv, span

regexSelector

You can select elements by using regexSelector instead of the selector property.

The regexSelector property has nodeName, attrName, and attrValue properties that are optional regular expression. So each of these enables to omit. It is AND condition if combinate.

{
  "childNodeRules": [
    {
      "regexSelector": {
        "nodeName": "/^[a-z]+$/",
        "attrName": "/^[a-z]+$/",
        "attrValue": "/^[a-z]+$/"
      },
      "rules": {
        "any-rule": "any-value"
      }
    }
  ]
}

You can use the capture of the regular expression. It evaluates the Mustache format in a value of the rules. It expands the capturing incremental number prepended $ mark as a variable.

{
  "childNodeRules": [
    {
      "regexSelector": {
        "attrName": "/^data-([a-z]+)$/"
      },
      "rules": {
        "any-rule": "It is {{ $1 }} data attribute",
        "any-rule2": {
          "value": "It is {{ $1 }} data attribute",
          "severity": "error"
        }
      }
    }
  ]
}

Of course, you can use the named capture group. It expands the name as a variable.

{
  "childNodeRules": [
    {
      "regexSelector": {
        "attrName": "/^data-(?<dataName>[a-z]+)$/"
      },
      "rules": {
        "any-rule": "It is {{ dataName }} data attribute"
      }
    }
  ]
}

Note: Recommend using named capture. The numbered capture may conflict and be overwritten.

{
  "childNodeRules": [
    {
      "regexSelector": {
        "attrName": "/^data-([a-z]+)$/", // It will be `$1`.
        "attrValue": "^(.+)$" // It will be `$1` too. `$1` is overwritten.
      },
      "rules": {
        "any-rule": "It is {{ $1 }} data attribute, and value is {{ $1 }}"
      }
    },
    {
      "regexSelector": {
        "attrName": "/^data-(?<dataName>[a-z]+)$/", // It will be `dataName`.
        "attrValue": "^(?<dataValue>.+)$" // It will be `dataValue`.
      },
      "rules": {
        "any-rule": "It is {{ dataName }} data attribute, and value is {{ dataValue }}"
      }
    }
  ]
}

You can select the element in complex conditions if you use the combination prop.

{
  "childNodeRules": [
    {
      "regexSelector": {
        "attrName": "img",
        "combination": {
          "combinator": ":has(~)",
          "nodeName": "source"
        }
      }
    }
  ]
}

The regexSelector above is the same as CSS selector img:has(~ source).

The combinator prop supports below:

  • " ": Descendant combinator
  • ">": Child combinator
  • "+": Next-sibling combinator
  • ":has(+)": Prev-sibling combinator
  • "~": Subsequent-sibling combinator
  • ":has(~)": Preceding-sibling

You can define nodes unlimitedly deeply.

{
  "childNodeRules": [
    {
      "regexSelector": {
        "attrName": "el1",
        "combination": {
          "combinator": " ",
          "attrName": "el2",
          "combination": {
            "combinator": ">",
            "attrName": "el3",
            "combination": {
              "combinator": "+",
              "attrName": "el4",
              "combination": {
                "combinator": "~",
                "attrName": "el5"
              }
            }
          }
        }
      }
    }
  ]
}

The above is the same as CSS selector el1 el2 > el3 + el4 ~ el5.

Usecase of regexSelector

overrides

You can override configurations to specific files if you specify the overrides option. It resolves glob format paths specified to a key.

{
  "rules": {
    "any-rule": true
  },
  "overrides": {
    "./path/to/**/*": {
      "rules": {
        "any-rule": false
      }
    }
  }
}

You can override options below: