Skip to content

Commit

Permalink
markdownlint script improvements (#39117)
Browse files Browse the repository at this point in the history
  • Loading branch information
rachmari authored Jul 19, 2023
1 parent 1f7e240 commit 712fe2d
Show file tree
Hide file tree
Showing 10 changed files with 264 additions and 247 deletions.
344 changes: 147 additions & 197 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"fixture-test": "cross-env ROOT=tests/fixtures npm test -- tests/rendering-fixtures",
"index-test-fixtures": "node src/search/scripts/index-elasticsearch.js -l en -l ja -V ghae -V dotcom --index-prefix tests -- src/search/tests/fixtures/search-indexes",
"lint": "eslint '**/*.{js,mjs,ts,tsx}'",
"lint-content": "src/content-linter/scripts/markdownlint.js",
"lint-translation": "cross-env NODE_OPTIONS=--experimental-vm-modules jest src/content-linter/tests/lint-files.js",
"openapi-docs": "src/rest/docs.js",
"playwright-test": "playwright test --project=\"Google Chrome\"",
Expand Down Expand Up @@ -110,6 +111,7 @@
"morgan": "^1.10.0",
"msgpack5rpc": "^1.1.0",
"next": "12.2.4",
"ora": "^6.3.1",
"parse5": "7.1.2",
"port-used": "^2.0.8",
"react": "^17.0.2",
Expand Down
8 changes: 8 additions & 0 deletions src/content-linter/lib/helpers/get-rules.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import rules from '../../../../node_modules/markdownlint/lib/rules.js'
import { gitHubDocsMarkdownlint } from '../linting-rules/index.js'
import { baseConfig } from '../../style/base.js'
import { githubDocsConfig } from '../../style/github-docs.js'

export const customRules = gitHubDocsMarkdownlint.rules
export const allRules = [...rules, ...gitHubDocsMarkdownlint.rules]
export const allConfig = { ...baseConfig, ...githubDocsConfig }
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { addError, ellipsify } from 'markdownlint-rule-helpers'

import { getCodeFenceTokens, getCodeFenceLines } from '../helpers.js'
import { getCodeFenceTokens, getCodeFenceLines } from '../helpers/utils.js'

export const codeFenceLineLength = {
names: ['GHD001', 'code-fence-line-length'],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { forEachInlineChild } from 'markdownlint-rule-helpers'

import { addFixErrorDetail } from '../helpers.js'
import { addFixErrorDetail } from '../helpers/utils.js'

export const imageAltTextEndPunctuation = {
names: ['GHD002', 'image-alt-text-end-punctuation'],
Expand Down
2 changes: 1 addition & 1 deletion src/content-linter/lib/linting-rules/image-file-kebab.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { forEachInlineChild } from 'markdownlint-rule-helpers'

import { addFixErrorDetail } from '../helpers.js'
import { addFixErrorDetail } from '../helpers/utils.js'

export const imageFileKebab = {
names: ['GHD004', 'image-file-kebab'],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { filterTokens } from 'markdownlint-rule-helpers'

import { addFixErrorDetail } from '../helpers.js'
import { addFixErrorDetail } from '../helpers/utils.js'
import { languageKeys } from '../../../../lib/languages.js'

export const internalLinksLang = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { filterTokens } from 'markdownlint-rule-helpers'

import { addFixErrorDetail } from '../helpers.js'
import { addFixErrorDetail } from '../helpers/utils.js'

export const internalLinksSlash = {
names: ['GHD006', 'internal-links-slash'],
Expand Down
147 changes: 102 additions & 45 deletions src/content-linter/scripts/markdownlint.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,75 +3,68 @@ import { program, Option } from 'commander'
import markdownlint from 'markdownlint'
import { applyFixes } from 'markdownlint-rule-helpers'
import { readFile, writeFile } from 'fs/promises'
import ora from 'ora'
import { extname } from 'path'
import { execSync } from 'child_process'

import walkFiles from '../../../script/helpers/walk-files.js'
import { gitHubDocsMarkdownlint } from '../lib/linting-rules/index.js'
import { baseConfig } from '../style/base.js'
import { githubDocsConfig } from '../style/github-docs.js'
import { allConfig, allRules, customRules } from '../lib/helpers/get-rules.js'

program
.description('Run GitHub Docs Markdownlint rules.')
.option(
'-p, --paths [paths...]',
'Specify filepaths to include. Default: changed and staged files',
)
.addOption(
new Option('-p, --paths [paths...]', 'Specify filepaths to include.').conflicts('files'),
new Option(
'-e, --errors',
'Only report rules that have the severity of error, not warning.',
).conflicts('rules'),
)
.addOption(new Option('-f, --files [files...]', 'A list of files to lint.').conflicts('paths'))
.option('--fix', 'Automatically fix errors that can be fixed.')
.addOption(
new Option('-e, --error', 'Only report rules that have the severity of error.').conflicts(
'rules',
),
new Option(
'--summary-by-rule',
'Summarize the number of errors for each rule, leaving out error details.',
).conflicts(['error', 'paths', 'rules', 'fix']),
)
.option('--verbose', 'Output additional error detail.')
.addOption(
new Option(
'-r, --rules [rules...]',
'Specify rules to run. For example code-fence-line-length.',
`Specify rules to run. For example, by short name MD001 or long name heading-increment \n${listRules()}\n\n`,
).conflicts('error'),
)
.option('--fix', 'Fix linting errors.')
.option('--summary-by-rule', 'Summarize the number of errors for each rule.')
.option('--verbose', 'Output full format for all errors.')
.parse(process.argv)

const { files, fix, paths, error, rules, summaryByRule, verbose } = program.opts()
const DEFAULT_LINT_DIRS = ['content', 'data']
const { fix, paths, errors, rules, summaryByRule, verbose } = program.opts()
const ALL_CONTENT_DIR = ['content', 'data']

main()

async function main() {
const start = Date.now()
// If paths has not been specified, lint all files
const files = getFilesToLint((summaryByRule && ALL_CONTENT_DIR) || paths || getChangedFiles())
const spinner = ora({ text: 'Running content linter', spinner: 'simpleDots' })

const config = {}
const allConfig = { ...baseConfig, ...githubDocsConfig }
if (error) {
const errorConfig = Object.keys(baseConfig).reduce(
(acc, key) => {
if (allConfig[key].severity === 'error') acc[key] = allConfig[key]
return acc
},
{ default: false },
)
Object.assign(config, errorConfig)
} else if (rules) {
config.default = false
for (const rule of rules) {
config[rule] = allConfig[rule]
}
} else {
Object.assign(config, allConfig)
if (!files.length) {
spinner.succeed('No files to lint')
process.exit(0)
}

// If paths or files have not been specified, lint all files
const filesToLint = files || getFilesToLint(paths || DEFAULT_LINT_DIRS)

const options = {
files: filesToLint,
config,
customRules: gitHubDocsMarkdownlint.rules,
}
spinner.start()
const start = Date.now()

// Initializes the config to pass to markdownlint based on the script options
const config = initializeConfig(errors, rules)
const options = { files, config, customRules }
const result = await markdownlint.promises.markdownlint(options)

// Apply markdownlint fixes if available and rewrite the files
if (fix) {
for (const file of filesToLint) {
for (const file of files) {
const content = await readFile(file, 'utf8')
const applied = applyFixes(content, result[file])
await writeFile(file, applied)
Expand All @@ -87,19 +80,25 @@ async function main() {
}

const end = Date.now()
console.log(`markdownlint finished in ${(end - start) / 1000} s`)
console.log(`🕦 Markdownlint finished in ${(end - start) / 1000} s`)

const errorFileCount = getErrorCountByFile(result)
if (errorFileCount > 0) {
console.log(`Found ${errorFileCount} file(s) with error(s).`)
spinner.fail(`Found ${errorFileCount} file(s) with error(s)`)
process.exit(1)
}
spinner.succeed('No errors found')
}

function getFilesToLint(paths) {
const fileList = []
for (const path of paths) {
fileList.push(...walkFiles(path, ['.md'], { includeBasePath: true }))
const lintableFilepaths = getLintableFilepaths(paths)
for (const path of lintableFilepaths) {
const isFile = extname(path) !== ''

isFile
? fileList.push(path)
: fileList.push(...walkFiles(path, ['.md'], { includeBasePath: true }))
}
return fileList
}
Expand Down Expand Up @@ -165,3 +164,61 @@ function formatResult(object) {
return acc
}, {})
}

// Get a list of changed and staged files in the local git repo
function getChangedFiles() {
const changedFiles = execSync(`git diff --name-only`)
.toString()
.trim()
.split('\n')
.filter(Boolean)
const stagedFiles = execSync(`git diff --name-only --staged`)
.toString()
.trim()
.split('\n')
.filter(Boolean)
return [...changedFiles, ...stagedFiles]
}

function getLintableFilepaths(paths) {
return paths.filter((path) => {
const extension = extname(path)
return extension ? extension === '.md' : true
})
}

function initializeConfig(errors, rules) {
const config = {}

if (errors) {
// Only configure the rules that have the severity of error
const errorConfig = Object.keys(baseConfig).reduce(
(acc, key) => {
if (allConfig[key].severity === 'error') acc[key] = allConfig[key]
return acc
},
{ default: false },
)
Object.assign(config, errorConfig)
} else if (rules) {
config.default = false
for (const rule of rules) {
config[rule] = allConfig[rule]
}
} else {
Object.assign(config, allConfig)
}

return config
}

// Summarizes the list of rules we have available to run with their
// short name, long name, and description.
function listRules() {
let ruleList = ''
for (const rule of allRules) {
if (!allConfig[rule.names[1]]) continue
ruleList += `\n[${rule.names[0]}, ${rule.names[1]}]: ${rule.description}`
}
return ruleList
}

0 comments on commit 712fe2d

Please sign in to comment.