build(aio): create noMarkdownHeadings content rule (#22759)

This content rule can check what markdown headings
appear in content properties.

PR Close #22759
This commit is contained in:
Pete Bacon Darwin 2018-03-13 11:22:54 +00:00 committed by Igor Minar
parent e0ae74d40e
commit 7a8c58162c
2 changed files with 109 additions and 0 deletions

View File

@ -0,0 +1,50 @@
/**
* A factory for creating a rule for the `checkContentRules` processor, which disallows markdown
* headings in a content property.
*
* @param {...number|string} disallowedHeadings
* Each parameter identifies heading levels that are not allowed. They can be in the form of:
*
* - a number (e.g. 1), which implies that the specified heading is not allowed
* - a range (e.g. '2,3'), which implies the range of headings that are not allowed
*
* (A range can be open ended on the upper bound by not specifying a value after the comma.)
*
* @example
* To create a rule that will only allow level 3 headings:
*
* ```
* const rule = createNoMarkdownHeadingRule(1, 2, '4,');
* ```
*
*/
module.exports = function createrNoMarkdownHeadingRule() {
const args = Array.prototype.slice.apply(arguments);
const disallowedHeadings = args.map(arg => `#{${arg}}`);
if (!disallowedHeadings.length) {
disallowedHeadings.push('#{1,}');
}
const regex = new RegExp(`^ {0,3}(${disallowedHeadings.join('|')}) +.*$`, 'mg');
return (doc, prop, value) => {
let match, matches = [];
while(match = regex.exec(value)) { // eslint-disable-line no-cond-assign
matches.push(match[0]);
}
if (matches.length) {
const list = listify(matches.map(match => `"${match}"`));
return `Invalid headings found in "${prop}" property: ${list}.`;
}
};
};
/**
* Convert an array of strings in to a human list - e.g separated by commas and the word `and`.
* @param {string[]} values The strings to convert to a list
*/
function listify(values) {
if (values.length <= 1) return values;
const last = values[values.length - 1];
const rest = values.slice(0, values.length - 1);
return [rest.join(', '), last].join(' and ');
}

View File

@ -0,0 +1,59 @@
const createNoMarkdownHeadings = require('./noMarkdownHeadings');
describe('createNoMarkdownHeadings rule', () => {
const noMarkdownHeadings = createNoMarkdownHeadings();
it('should return `undefined` if there is no heading in a value', () => {
expect(noMarkdownHeadings({}, 'description', 'some ## text'))
.toBeUndefined();
});
it('should return an error message if there is a markdown heading in a single line value', () => {
expect(noMarkdownHeadings({}, 'description', '# heading 1'))
.toEqual('Invalid headings found in "description" property: "# heading 1".');
});
it('should return an error message if there is a markdown heading in a multiline value', () => {
expect(noMarkdownHeadings({}, 'description', 'some text\n# heading 1'))
.toEqual('Invalid headings found in "description" property: "# heading 1".');
});
it('should cope with up to 3 spaces before the heading marker', () => {
expect(noMarkdownHeadings({}, 'description', ' # heading 1'))
.toEqual('Invalid headings found in "description" property: " # heading 1".');
expect(noMarkdownHeadings({}, 'description', ' # heading 1'))
.toEqual('Invalid headings found in "description" property: " # heading 1".');
expect(noMarkdownHeadings({}, 'description', ' # heading 1'))
.toEqual('Invalid headings found in "description" property: " # heading 1".');
});
it('should return an error message for each heading found', () => {
expect(noMarkdownHeadings({}, 'description', '# heading 1\nsome text\n## heading 2\nmore text\n### heading 3'))
.toEqual('Invalid headings found in "description" property: "# heading 1", "## heading 2" and "### heading 3".');
});
describe('(specified heading levels)', () => {
it('should take heading levels into account', () => {
const noTopLevelHeadings = createNoMarkdownHeadings(1);
expect(noTopLevelHeadings({}, 'description', '# top level'))
.toEqual('Invalid headings found in "description" property: "# top level".');
expect(noTopLevelHeadings({}, 'description', '## second level'))
.toBeUndefined();
expect(noTopLevelHeadings({}, 'description', '### third level'))
.toBeUndefined();
expect(noTopLevelHeadings({}, 'description', '#### fourth level'))
.toBeUndefined();
const allowLevel3Headings = createNoMarkdownHeadings(1, 2, '4,');
expect(allowLevel3Headings({}, 'description', '# top level'))
.toEqual('Invalid headings found in "description" property: "# top level".');
expect(allowLevel3Headings({}, 'description', '## second level'))
.toEqual('Invalid headings found in "description" property: "## second level".');
expect(allowLevel3Headings({}, 'description', '### third level'))
.toBeUndefined();
expect(allowLevel3Headings({}, 'description', '#### fourth level'))
.toEqual('Invalid headings found in "description" property: "#### fourth level".');
});
});
});