# Content Linter For an overview of what the content linter is and how to use it, see [Using the content linter](/content/contributing/collaborating-on-github-docs/using-the-content-linter.md). This README shows you how to contribute to the content linter code by adding new rules, modifying existing rules, or updating the scripts used to run the content linter. ## Step-by-step workflow Before creating a new rule, check that it doesn't already exist in [Markdownlint](https://github.com/DavidAnson/markdownlint/#rules--aliases) or [open-source plugins](https://www.npmjs.com/search?q=keywords:markdownlint-rule). ### 1. Create rule file Create a new file in `src/content-linter/lib/linting-rules` directory. File name should match the rule name (e.g., `no-whitespace.ts`). Use the template above or review existing rules for patterns. ### 2. Add to custom rules array Import the rule and add it to the `rules` array in `src/content-linter/lib/linting-rules/index.ts`. ### 3. Configure the rule Add configuration to `src/content-linter/style/github-docs.ts`: - **`githubDocsConfig`** - Primary area for new rules - **`githubDocsFrontmatterConfig`** - Rules that check frontmatter and need frontmatter line numbers - **`searchReplaceConfig`** - Simple string/regex checks Required properties: - `severity`: `'error'` (default) or `'warning'` - `'partial-markdown-files'`: `true` if rule can run on data directory files - `'precommitSeverity'`: Optional different severity for local commits ### 4. Add unit test Create test file in `src/content-linter/tests/unit` with same name as rule file. Test positive/negative cases, line numbers, ranges, and auto-fixes. ### Quick reference **Rule creation checklist:** - [ ] Create rule file in `src/content-linter/lib/linting-rules/` - [ ] Import and add to `src/content-linter/lib/linting-rules/index.ts` - [ ] Register in `src/content-linter/style/github-docs.ts` - [ ] Add unit tests in `src/content-linter/tests/unit/` **Locations:** - Rule logic: `src/content-linter/lib/linting-rules/*.ts` - Rule registration: `src/content-linter/lib/linting-rules/index.ts` - Rule configuration: `src/content-linter/style/github-docs.ts` - Unit tests: `src/content-linter/tests/unit/*.ts` **Common patterns:** - Rule IDs: `GHD###` format (start with GHD030+ for upstream candidates) - Frontmatter rules: Use `githubDocsFrontmatterConfig` section - Simple string checks: Use `searchReplaceConfig` section - Error severity: Default to `'error'`, use `'warning'` sparingly **Rule template:** ```typescript import { addError } from 'markdownlint-rule-helpers' export const ruleNameHere = { names: ['GHD###', 'descriptive-rule-name'], description: 'One sentence description without ending punctuation', tags: ['appropriate', 'tags'], parser: 'markdownit', function: (params, onError) => { // Rule logic here addError(onError, lineNumber, description, line, range, fixInfo) }, } ``` ## Rule development details ### Helper utilities Use helper functions from `markdownlint-rule-helpers` instead of custom logic. Review [`utils`](/src/content-linter/lib/helpers/utils.ts) and [`liquid-utils`](/src/content-linter/lib/helpers/liquid-utils.ts) for GitHub Docs-specific helpers. ### Setting errors Import error functions from `markdownlint-rule-helpers`. Use `addError` for most cases, `addErrorContext` for specific ranges, `addErrorDetailIf` for expected vs actual results. ### Async rules To use asynchronous code, set `asynchronous: true` in the exported object. See [Markdownlint async documentation](https://github.com/DavidAnson/markdownlint/blob/main/doc/CustomRules.md#asynchronous-rules). ### Reading the data directory Use `getDataByLanguage` or `getDeepDataByLanguage` from [`lib/get-data.ts`](/lib/get-data.ts) for testable data access. See [`liquid-data-tags.ts`](/src/content-linter/lib/linting-rules/liquid-data-tags.ts) for examples. ### Rule metadata **names**: First name is rule ID (`GHD###` format), second is readable name matching file name. Rules for upstream use start with `GHD03X`. **description**: One sentence describing the violation without end punctuation. **tags**: Categorize rules using existing tags (see table below). #### Tags for rule categories | Tag | Description | | --- | --- | | `code` | Rules that check for violations in code blocks. | | `images` | Rules that check for violations in image tags. | | `links` | Rules that check for violations in links. | | `url` | Rules that check for violations in URLs. | | `ul` | Rules that check for violations in unordered lists. | | `ol` | Rules that check for violations in ordered lists. | | `accessibility` | Rules that check for accessibility violations. | | `format` | Rules that check for formatting violations. | | `single-source` | Rules that check for violations in single-sourced content (e.g., data reusable or variable usage enforcement). | | `frontmatter` | Rules that check for violations in frontmatter. | | `liquid` | Rules that check for violations in Liquid. | | `versioning` | Rules that check for violations in Liquid versioning. Rules with this tag typically have the `liquid` tag too. | | `feature` | Rules that check for violations specific to a feature in the docs platform (e.g., early-access, code annotations) or a GitHub feature (e.g., GitHub Actions). Rules with this tag should also include a tag with the feature name. | | `annotate` | Rules that check for violations in code annotations. Rules with this tag should also include the `feature` tag. | | `actions` | Rules that check for violations in GitHub Actions. Rules with this tag should also include the `feature` tag. | | `early-access` | Rules that check for violations in early-access content. Rules with this tag should also include the `feature` tag. | ## Configuration reference Rules are configured in `src/content-linter/style/github-docs.ts` with these sections: - **`githubDocsConfig`** - Primary area for new rules (frontmatter separated automatically) - **`githubDocsFrontmatterConfig`** - Rules that check frontmatter and need frontmatter line numbers - **`githubMarkdownlintConfig`** - Rules from [markdownlint-github](https://github.com/github/markdownlint-github) - **`searchReplaceConfig`** - Simple search/replace checks ### Configuration options **severity**: `'error'` (enforced) or `'warning'` (displayed but not enforced). Default to `'error'`. **partial-markdown-files**: `true` if rule can run on data directory files, `false` otherwise. **precommitSeverity**: Optional different severity for local commits vs CI. Rarely needed. ### Testing rules Test on real content: ```shell npm run lint-content -- --paths --rules ``` Unit tests must include positive/negative cases, line numbers, ranges, and auto-fix testing. ## Updating content for new rules When adding error-level rules with many existing violations: 1. **Autofix**: Use the rule's auto-fix if available 2. **Disable comments**: Use `disable-rules.ts` to add disable comments 3. **Manual fixes**: Fix violations manually (most time-consuming) 4. **Warning severity**: Temporarily set to `warning` if too many violations exist ## Scripts and tools - [`lint-content.ts`](/src/content-linter/scripts/lint-content.ts) - Primary script for running rules against content - [`disable-rules.ts`](/src/content-linter/scripts/disable-rules.ts) - Automatically adds disable comments for rule violations - [`pretty-print-results.ts`](/src/content-linter/scripts/pretty-print-results.ts) - Formats console output ## Using the search-replace plugin Add simple string/regex checks to the `searchReplaceConfig` rules array in [`github-docs.ts`](/src/content-linter/style/github-docs.ts). **Note**: Regexes must be double-escaped (e.g., `/\\./` not `/\./`). Test regexes at [regexr.com](https://regexr.com/). **Limitation**: Cannot disable individual search-replace rules - must disable all with ``. ## Adding context to base rules To add context to base rule error messages, add a `context` property in [`base.ts`](/src/content-linter/style/base.ts): ```javascript 'fenced-code-language': { severity: 'error', context: 'When you add a fenced code block, you must specify the code language...', }, ``` ## System architecture The linter system has multiple moving parts: ### Three reporting surfaces 1. **CI** - runs `lint-content.ts` on diffed paths 2. **precommit** - runs `lint-content.ts` with `--precommit` flag on diffed paths 3. **automated report** - runs `lint-content.ts` on all paths, then `lint-report.ts` ### Severity system - **Two severities**: `warning` and `error` - **Two severity types**: `severity` and `precommitSeverity` ### Four linter types 1. **Native markdownlint** (in `base.ts`) 2. **GitHub markdown linters** (in `github-docs.ts`) 3. **Markdownlint search-replace** (in `github-docs.ts`) 4. **Custom docs linters** (in `github-docs.ts` + individual files in `src/content-linter/lib/linting-rules/`) Only type #4 requires individual rule files - the rest are imports.