Skip to content

Commit

Permalink
Add rules to check the sections
Browse files Browse the repository at this point in the history
* `no-unknown-sections`: checks that unknown sections are placed in the
  extra sections
* `require-sections`: checks that required sections are used
* `section-order`: checks that sections are used in the prescribed
  outline order

Related to GH-4.
  • Loading branch information
wooorm committed Nov 16, 2017
1 parent 3eddaf2 commit 358db2b
Show file tree
Hide file tree
Showing 8 changed files with 817 additions and 7 deletions.
18 changes: 14 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,20 @@ This preset configures [`remark-lint`][lint] with the following rules:

* [`file-extension`](https://github.com/RichardLitt/standard-readme-preset/blob/master/rules/file-extension.js)
— Check that `md` is used as a file extension
* [`file-stem`](https://github.com/RichardLitt/standard-readme-preset/blob/master/rules/file-stem.js)
— Check that `README` is used as a file stem (allows i18n: `README.de`, `README.en-GB`)
* [`require-file-extension`](https://github.com/RichardLitt/standard-readme-preset/blob/master/rules/require-file-extension.js)
— Check that a file extension is used
* [`file-stem`](https://github.com/RichardLitt/standard-readme-preset/blob/master/rules/file-stem.js)
— Check that `README` is used as a file stem (allows i18n: `README.de`, `README.en-GB`)
* [`require-file-extension`](https://github.com/RichardLitt/standard-readme-preset/blob/master/rules/require-file-extension.js)
— Check that a file extension is used
* [`no-unknown-sections`](https://github.com/RichardLitt/standard-readme-preset/blob/master/rules/no-unknown-sections.js)
— Check that only known sections are used, except for in the extra sections
* [`require-sections`](https://github.com/RichardLitt/standard-readme-preset/blob/master/rules/require-sections.js)
— Check that required sections (`contribute`, `license`) exist.
`table-of-contents` is required if `toc: true` is given, optional for
`toc: false`, and otherwise inferred based on if the number of lines in the
file, excluding the ToC itself, exceeds 100.
`install` and `usage` are required if `installable: true` is given.
* [`section-order`](https://github.com/RichardLitt/standard-readme-preset/blob/master/rules/section-order.js)
— Check that sections are used in the order they’re supposed to

## Contribute

Expand Down
5 changes: 4 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,8 @@ exports.plugins = [
require('remark-lint-appropriate-heading'),
require('./rules/file-stem'),
require('./rules/file-extension'),
require('./rules/require-file-extension')
require('./rules/require-file-extension'),
require('./rules/no-unknown-sections'),
require('./rules/require-sections'),
require('./rules/section-order')
]
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,20 @@
],
"files": [
"index.js",
"rules"
"rules",
"util"
],
"coordinates": [
52.5006656,
13.4193688
],
"dependencies": {
"github-slugger": "^1.2.0",
"mdast-util-to-string": "^1.0.4",
"remark-lint-appropriate-heading": "^2.0.2",
"unified-lint-rule": "^1.0.2"
"unified-lint-rule": "^1.0.2",
"unist-util-position": "^3.0.0",
"unist-util-stringify-position": "^1.1.1"
},
"devDependencies": {
"nyc": "^11.3.0",
Expand Down
74 changes: 74 additions & 0 deletions rules/no-unknown-sections.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
var rule = require('unified-lint-rule')
var position = require('unist-util-position')
var stringify = require('unist-util-stringify-position')
var schema = require('../util/schema')

module.exports = rule('standard-readme:no-unknown-sections', noUnknownSections)

var head = [
'table-of-contents',
'security',
'background',
'install',
'usage'
]

var tail = [
'api',
'maintainer',
'maintainers',
'contribute',
'license'
]

function noUnknownSections (ast, file) {
var sections = schema(ast)
var length = sections.length
var index = -1
var state = 'head'
var customPosition
var tailStart
var inHead
var inTail
var section

while (++index < length) {
section = sections[index]
inHead = head.indexOf(section.id) !== -1
inTail = tail.indexOf(section.id) !== -1

if (state === 'head') {
if (!inHead) {
if (inTail) {
state = 'tail'
tailStart = position.start(section.node)
} else {
state = 'custom'
customPosition = {start: position.start(section.node)}
}
}
} else if (state === 'tail') {
if (!inTail) {
if (customPosition) {
file.message('Unexpected unknown heading in tail: move it to the extra sections (' + stringify(customPosition) + ')', section.node)
} else {
file.message('Unexpected unknown section in tail: move it in front of the tail (' + stringify(tailStart) + ')', section.node)
}
}
} else {
customPosition.end = {
line: position.end(section.node).line - 1,
column: 1
}

if (inTail) {
state = 'tail'
tailStart = position.start(section.node)
} else if (inHead) {
/* We had a custom section, and now there’s an entry that should’ve
* been in the head. Suggest switching them around. */
file.message('Unexpected header section after extra sections (' + stringify(customPosition) + '): move it in front of the extra sections', section.node)
}
}
}
}
68 changes: 68 additions & 0 deletions rules/require-sections.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
var rule = require('unified-lint-rule')
var position = require('unist-util-position')
var schema = require('../util/schema')

module.exports = rule('standard-readme:require-sections', requireSections)

/* Max allowed file-size without a table of contents. */
var maxLinesWithoutTableOfContents = 100

/* Pretty heading names people *should* use. */
var pretty = {
'table-of-contents': 'Table of Contents',
install: 'Install',
usage: 'Usage',
contribute: 'Contribute',
license: 'License'
}

var alwaysRequired = [
'contribute',
'license'
]

function requireSections (ast, file, options) {
var sections = schema(ast)
var required = alwaysRequired.concat()
var settings = options || {}

if (settings.toc === null || settings.toc === undefined ? inferToc(ast, sections) : settings.toc) {
required.push('table-of-contents')
}

if (settings.installable) {
required.push('install', 'usage')
}

sections = sections.map(id)
required.forEach(check)

function check (slug) {
if (sections.indexOf(slug) === -1) {
file.message('Missing required `' + pretty[slug] + '` section')
}
}
}

function inferToc (tree, sections) {
var lines = position.end(tree).line
var length = sections.length
var index = -1
var section
var end

while (++index < length) {
section = sections[index]

if (section.id === 'table-of-contents') {
end = index === length - 1 ? section.node : sections[index + 1].node
lines -= position.end(end).line - position.start(section.node).line
}
}

return lines > maxLinesWithoutTableOfContents
}

function id (info) {
return info.id
}
70 changes: 70 additions & 0 deletions rules/section-order.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
var rule = require('unified-lint-rule')
// var position = require('unist-util-position')
var stringify = require('unist-util-stringify-position')
var schema = require('../util/schema')

module.exports = rule('standard-readme:section-order', sectionOrder)

var order = [
'table-of-contents',
'security',
'background',
'install',
'usage',
'api',
/* Note: maintainer and maintainers don’t matter in this case... */
'maintainer',
'maintainers',
'contribute',
'license'
]

function sectionOrder (ast, file) {
var sections = schema(ast)
var byId = {}
var length = sections.length
var index = -1
var actual = []
var expected
var section
var id
var alt
var idx

/* First, get all sections usd in the document. */
while (++index < length) {
section = sections[index]
id = section.id

if (order.indexOf(id) !== -1) {
actual.push(id)
byId[id] = section.node
}
}

/* Find the expected order for the used headings. */
expected = actual.concat().sort(sort)

/* Check if that’s used! */
length = expected.length
index = -1

while (++index < length) {
id = expected[index]
alt = actual[index]

if (id !== alt) {
file.message('Expected `' + id + '` before `' + alt + '` (' + stringify(byId[alt]) + ')', byId[id])

/* Apply the change. */
idx = actual.indexOf(id)

actual.splice(index, 0, id) /* Insert. */
actual.splice(idx + 1, 1) /* Remove. */
}
}
}

function sort (a, b) {
return order.indexOf(a) - order.indexOf(b)
}
Loading

0 comments on commit 358db2b

Please sign in to comment.